Compare commits
	
		
			21 Commits
		
	
	
		
			gbsneto/ef
			...
			wip/new-no
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | bf374ccabb | ||
|   | 02060d2bbc | ||
|   | 8750f1edc0 | ||
|   | 798f17a97d | ||
|   | a4091adbf2 | ||
|   | ecf795b6ef | ||
|   | 75447b249c | ||
|   | 26d2fb8a37 | ||
|   | f7223763d2 | ||
|   | 0b414308fb | ||
|   | 060917ae2b | ||
|   | 9f2e5b9b51 | ||
|   | 4a07eb77f6 | ||
|   | fbe379c81c | ||
|   | a07e8bbf37 | ||
|   | a6aabb1d3a | ||
|   | aef6273fed | ||
|   | be8b1c7d2d | ||
|   | 14bc748cea | ||
|   | c32917f6c1 | ||
|   | 182d45dace | 
| @@ -414,10 +414,7 @@ StScrollBar StButton#vhandle:active { | ||||
| /* Buttons */ | ||||
|  | ||||
| .candidate-page-button, | ||||
| .notification-button, | ||||
| .notification-icon-button, | ||||
| .hotplug-notification-item, | ||||
| .hotplug-resident-eject-button, | ||||
| .modal-dialog-button, | ||||
| .app-view-control { | ||||
|     border: 1px solid #8b8b8b; | ||||
| @@ -431,17 +428,12 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .candidate-page-button:hover, | ||||
| .notification-button:hover, | ||||
| .notification-icon-button:hover, | ||||
| .hotplug-notification-item:hover, | ||||
| .hotplug-resident-eject-button:hover, | ||||
| .modal-dialog-button:hover { | ||||
|     background-gradient-start: rgba(255, 255, 255, 0.3); | ||||
|     background-gradient-end: rgba(255, 255, 255, 0.1); | ||||
| } | ||||
|  | ||||
| .notification-button:focus, | ||||
| .notification-icon-button:focus, | ||||
| .hotplug-notification-item:focus, | ||||
| .modal-dialog-button:focus, | ||||
| .app-view-control:focus { | ||||
| @@ -455,10 +447,7 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| .candidate-page-button:active, | ||||
| .candidate-page-button:pressed, | ||||
| .notification-button:active, | ||||
| .notification-icon-button:active, | ||||
| .hotplug-notification-item:active, | ||||
| .hotplug-resident-eject-button:active, | ||||
| .modal-dialog-button:active, | ||||
| .modal-dialog-button:pressed, | ||||
| .app-view-control:checked { | ||||
| @@ -467,8 +456,6 @@ StScrollBar StButton#vhandle:active { | ||||
| } | ||||
|  | ||||
| .candidate-page-button:insensitive, | ||||
| .notification-button:insensitive, | ||||
| .notification-icon-button:insensitive, | ||||
| .modal-dialog-button:insensitive { | ||||
|     border-color: #666666; | ||||
|     color: #9f9f9f; | ||||
| @@ -480,7 +467,6 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| #searchEntry, | ||||
| .modal-dialog-button, | ||||
| .notification-button, | ||||
| .hotplug-notification-item, | ||||
| .app-view-controls, | ||||
| #screenShieldNotifications { | ||||
| @@ -1505,105 +1491,85 @@ StScrollBar StButton#vhandle:active { | ||||
|  | ||||
| /* Message Tray */ | ||||
|  | ||||
| #message-tray { | ||||
|     background: #2e3436 url(message-tray-background.png); | ||||
|     background-repeat: repeat; | ||||
|     height: 72px; | ||||
| .notification-drawer { | ||||
|     background: rgba(0,0,0,0.8); | ||||
|     padding: 1em; | ||||
|     border: 2px solid #4c4c4c; | ||||
|     border-bottom: 0px; | ||||
|     border-radius: 6px 6px 0 0; | ||||
| } | ||||
|  | ||||
| .message-tray-summary { | ||||
|     height: 72px; | ||||
| .system-tray-icons, | ||||
| .notification-drawer-footer-actions { | ||||
|     spacing: 0.5em; | ||||
| } | ||||
|  | ||||
| .message-tray-menu-button StIcon { | ||||
|     padding: 0 20px; | ||||
|     color: #aaaaaa; | ||||
|     icon-size: 24px; | ||||
| .system-tray-icon-button, | ||||
| .notification-drawer-button { | ||||
|     border-radius: 4px; | ||||
|     border: 1px solid #4c4c4c; | ||||
|     padding: 4px; | ||||
| } | ||||
|  | ||||
| .message-tray-menu-button:hover StIcon, | ||||
| .message-tray-menu-button:active StIcon, | ||||
| .message-tray-menu-button:focus StIcon { | ||||
|     color: #eeeeee; | ||||
| .system-tray-icon-button { | ||||
|     width: 22px; | ||||
|     height: 22px; | ||||
| } | ||||
|  | ||||
| .notification-drawer-button StIcon { | ||||
|     icon-size: 22px; | ||||
| } | ||||
|  | ||||
| .system-tray-icon-button:hover, | ||||
| .notification-drawer-button:hover { | ||||
|     background: 1px solid #4c4c4c; | ||||
| } | ||||
|  | ||||
| .url-highlighter { | ||||
|     link-color: #ccccff; | ||||
| } | ||||
|  | ||||
| .no-messages-label { | ||||
|     color: #999999; | ||||
| } | ||||
|  | ||||
| .notification { | ||||
|     border-radius: 10px 10px 0px 0px; | ||||
|     background: rgba(0,0,0,0.9); | ||||
|     padding: 8px 8px 4px 8px; | ||||
|     spacing-rows: 4px; | ||||
|     spacing-columns: 10px; | ||||
| } | ||||
|  | ||||
| .notification, #notification-container { | ||||
| .notification, #notification-container, .notification-drawer { | ||||
|     font-size: 11pt; | ||||
|     width: 34em; | ||||
| } | ||||
|  | ||||
| .notification.multi-line-notification { | ||||
|     padding-bottom: 8px; | ||||
| .notification-main-content { | ||||
|     padding: 8px; | ||||
|     spacing: 8px; | ||||
|     border-radius: 10px 10px 0px 0px; | ||||
|     background: rgba(0,0,0,0.8); | ||||
| } | ||||
|  | ||||
| .notification-unexpanded { | ||||
|     /* We want to force the actor at a specific size, irrespective | ||||
|        of its minimum and preferred size, so we override both */ | ||||
|     min-height: 36px; | ||||
|     height: 36px; | ||||
| .notification-action-area { | ||||
|     padding: 8px; | ||||
| } | ||||
|  | ||||
| /* We use row-span = 2 for the image cell, which prevents its height preferences to be | ||||
|    taken into account during allocation, so its height ends up being limited by the height | ||||
|    of the content in the other rows. To avoid showing a stretched image, we set the minimum | ||||
|    height of the table to be ICON_SIZE + IMAGE_SIZE + spacing-rows = 24 + 125 + 10 = 159 */ | ||||
| .notification-with-image { | ||||
|     min-height: 159px; | ||||
| .notification-action-area, | ||||
| .notification-button { | ||||
|     background: rgba(0,0,0,0.8); | ||||
|     border-top: 1px solid #666; | ||||
| } | ||||
|  | ||||
| .summary-boxpointer { | ||||
|     -arrow-border-radius: 15px; | ||||
|     -arrow-background-color: rgba(0,0,0,0.9); | ||||
|     -arrow-base: 36px; | ||||
|     -arrow-rise: 18px; | ||||
|     color: white; | ||||
|     -boxpointer-gap: 4px; | ||||
| .notification-button { | ||||
|     padding: 8px 0px; | ||||
|     border-right: 1px solid #666; | ||||
| } | ||||
|  | ||||
| .summary-boxpointer .notification { | ||||
|     border-radius: 9px; | ||||
|     background: rgba(0,0,0,0) !important; | ||||
|     padding-bottom: 12px; | ||||
| .notification-button:hover { | ||||
|     background: rgba(255,255,255,0.3); | ||||
| } | ||||
|  | ||||
| .summary-boxpointer #summary-right-click-menu { | ||||
|     padding-top: 12px; | ||||
|     padding-bottom: 12px; | ||||
| .notification-button:active { | ||||
|     background: rgba(255,255,255,0.1); | ||||
| } | ||||
|  | ||||
| .summary-notification-stack-scrollview { | ||||
|     max-height: 18em; | ||||
|     padding-top: 8px; | ||||
|     padding-bottom: 8px; | ||||
| .notification-button:last-child { | ||||
|     border-right-width: 0px; | ||||
| } | ||||
|  | ||||
| .summary-notification-stack-scrollview:ltr { | ||||
|     padding-right: 8px; | ||||
| } | ||||
|  | ||||
| .summary-notification-stack-scrollview:rtl { | ||||
|     padding-left: 8px; | ||||
| } | ||||
|  | ||||
| .notification-scrollview { | ||||
|     max-height: 10em; | ||||
|     -st-vfade-offset: 24px; | ||||
| .notification-title-box { | ||||
|     spacing: 8px; | ||||
| } | ||||
|  | ||||
| .notification-scrollview:ltr > StScrollBar { | ||||
| @@ -1614,37 +1580,9 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-right: 6px; | ||||
| } | ||||
|  | ||||
| .notification-body { | ||||
|     spacing: 5px; | ||||
| } | ||||
|  | ||||
| .notification-actions { | ||||
|     padding-top: 18px; | ||||
|     spacing: 10px; | ||||
| } | ||||
|  | ||||
| .notification-button { | ||||
|     -st-natural-width: 140px; | ||||
|     padding: 4px 4px 5px; | ||||
| } | ||||
|  | ||||
| .notification-button:focus { | ||||
|     -st-natural-width: 138px; | ||||
|     padding: 3px 4px 4px; | ||||
| } | ||||
|  | ||||
| .notification-icon-button { | ||||
|     border-radius: 5px; | ||||
|     padding: 5px; | ||||
| } | ||||
|  | ||||
| .notification-icon-button:focus { | ||||
|     padding: 4px; | ||||
| } | ||||
|  | ||||
| .notification-icon-button > StIcon { | ||||
|     icon-size: 16px; | ||||
|     padding: 8px; | ||||
| .notification-scrollview { | ||||
|     max-height: 10em; | ||||
|     -st-vfade-offset: 24px; | ||||
| } | ||||
|  | ||||
| .secondary-icon { | ||||
| @@ -1669,45 +1607,6 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding: 2px 5px; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-box { | ||||
|     spacing: 8px; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-mount { | ||||
|     spacing: 8px; | ||||
|     border-radius: 4px; | ||||
|  | ||||
|     color: #ccc; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-mount:hover { | ||||
|     background-gradient-direction: horizontal; | ||||
|     background-gradient-start: rgba(255, 255, 255, 0.1); | ||||
|     background-gradient-end: rgba(255, 255, 255, 0); | ||||
|  | ||||
|     color: #fff; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-mount-label { | ||||
|     color: inherit; | ||||
|     padding-left: 6px; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-mount-icon { | ||||
|     icon-size: 24px; | ||||
|     padding-left: 6px; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-eject-icon { | ||||
|     icon-size: 16px; | ||||
| } | ||||
|  | ||||
| .hotplug-resident-eject-button { | ||||
|     padding: 7px; | ||||
|     border-radius: 5px; | ||||
|     color: #ccc; | ||||
| } | ||||
|  | ||||
| .chat-log-message { | ||||
|     color: #888888; | ||||
| } | ||||
| @@ -1747,7 +1646,11 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-right: 4px; | ||||
| } | ||||
|  | ||||
| .chat-notification-scrollview{ | ||||
| .chat-notification-body-box { | ||||
|     spacing: 5px; | ||||
| } | ||||
|  | ||||
| .chat-notification-scrollview { | ||||
|     max-height: 22em; | ||||
| } | ||||
|  | ||||
| @@ -1797,6 +1700,28 @@ StScrollBar StButton#vhandle:active { | ||||
|     -shell-counter-overlap-y: 13px; | ||||
| } | ||||
|  | ||||
| .message-tray-indicator { | ||||
|     spacing: 4px; | ||||
| } | ||||
|  | ||||
| .message-tray-indicator-count { | ||||
|     font-size: 1.2em; | ||||
|     font-weight: bold; | ||||
|  | ||||
|     color: black; | ||||
|     background-color: rgba(255, 255, 255, 0.7); | ||||
|     border-radius: 1em; | ||||
|     padding: 1em; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .message-tray-indicator-glow { | ||||
|     height: 4px; | ||||
|     background-gradient-start: rgba(255, 255, 255, 0); | ||||
|     background-gradient-end: rgba(255, 255, 255, 1); | ||||
|     background-gradient-direction: vertical; | ||||
| } | ||||
|  | ||||
| /* OSD */ | ||||
| .osd-window { | ||||
|     text-align: center; | ||||
| @@ -2666,8 +2591,7 @@ StScrollBar StButton#vhandle:active { | ||||
|     padding-bottom: 0px; | ||||
| } | ||||
|  | ||||
| #screenShieldNotifications .notification-button, | ||||
| #screenShieldNotifications .notification-icon-button { | ||||
| #screenShieldNotifications .notification-button { | ||||
|     border: 1px rgba(255,255,255,0.5); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -170,17 +170,6 @@ const AutorunManager = new Lang.Class({ | ||||
|         this._transDispatcher = new AutorunTransientDispatcher(this); | ||||
|     }, | ||||
|  | ||||
|     _ensureResidentSource: function() { | ||||
|         if (this._residentSource) | ||||
|             return; | ||||
|  | ||||
|         this._residentSource = new AutorunResidentSource(this); | ||||
|         let destroyId = this._residentSource.connect('destroy', Lang.bind(this, function() { | ||||
|             this._residentSource.disconnect(destroyId); | ||||
|             this._residentSource = null; | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     enable: function() { | ||||
|         this._scanMounts(); | ||||
|  | ||||
| @@ -189,17 +178,12 @@ const AutorunManager = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         if (this._residentSource) | ||||
|             this._residentSource.destroy(); | ||||
|         this._volumeMonitor.disconnect(this._mountAddedId); | ||||
|         this._volumeMonitor.disconnect(this._mountRemovedId); | ||||
|     }, | ||||
|  | ||||
|     _processMount: function(mount, hotplug) { | ||||
|         let discoverer = new ContentTypeDiscoverer(Lang.bind(this, function(mount, apps, contentTypes) { | ||||
|             this._ensureResidentSource(); | ||||
|             this._residentSource.addMount(mount, apps); | ||||
|  | ||||
|             if (hotplug) | ||||
|                 this._transDispatcher.addMount(mount, apps, contentTypes); | ||||
|         })); | ||||
| @@ -224,8 +208,6 @@ const AutorunManager = new Lang.Class({ | ||||
|  | ||||
|     _onMountRemoved: function(monitor, mount) { | ||||
|         this._transDispatcher.removeMount(mount); | ||||
|         if (this._residentSource) | ||||
|             this._residentSource.removeMount(mount); | ||||
|     }, | ||||
|  | ||||
|     ejectMount: function(mount) { | ||||
| @@ -288,153 +270,6 @@ const AutorunManager = new Lang.Class({ | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const AutorunResidentSource = new Lang.Class({ | ||||
|     Name: 'AutorunResidentSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(manager) { | ||||
|         this.parent(_("Removable Devices"), 'media-removable'); | ||||
|         this.resident = true; | ||||
|  | ||||
|         this._mounts = []; | ||||
|  | ||||
|         this._manager = manager; | ||||
|         this._notification = new AutorunResidentNotification(this._manager, this); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationPolicy({ showInLockScreen: false }); | ||||
|     }, | ||||
|  | ||||
|     buildRightClickMenu: function() { | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     addMount: function(mount, apps) { | ||||
|         if (!shouldAutorunMount(mount, false)) | ||||
|             return; | ||||
|  | ||||
|         let filtered = this._mounts.filter(function (element) { | ||||
|             return (element.mount == mount); | ||||
|         }); | ||||
|  | ||||
|         if (filtered.length != 0) | ||||
|             return; | ||||
|  | ||||
|         let element = { mount: mount, apps: apps }; | ||||
|         this._mounts.push(element); | ||||
|         this._redisplay(); | ||||
|     }, | ||||
|  | ||||
|     removeMount: function(mount) { | ||||
|         this._mounts = | ||||
|             this._mounts.filter(function (element) { | ||||
|                 return (element.mount != mount); | ||||
|             }); | ||||
|  | ||||
|         this._redisplay(); | ||||
|     }, | ||||
|  | ||||
|     _redisplay: function() { | ||||
|         if (this._mounts.length == 0) { | ||||
|             this._notification.destroy(); | ||||
|             this.destroy(); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this._notification.updateForMounts(this._mounts); | ||||
|  | ||||
|         // add ourselves as a source, and push the notification | ||||
|         if (!Main.messageTray.contains(this)) { | ||||
|             Main.messageTray.add(this); | ||||
|             this.pushNotification(this._notification); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const AutorunResidentNotification = new Lang.Class({ | ||||
|     Name: 'AutorunResidentNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(manager, source) { | ||||
|         this.parent(source, source.title, null, { customContent: true }); | ||||
|  | ||||
|         // set the notification as resident | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this._layout = new St.BoxLayout ({ style_class: 'hotplug-resident-box', | ||||
|                                            vertical: true }); | ||||
|         this._manager = manager; | ||||
|  | ||||
|         this.addActor(this._layout, | ||||
|                       { x_expand: true, | ||||
|                         x_fill: true }); | ||||
|     }, | ||||
|  | ||||
|     updateForMounts: function(mounts) { | ||||
|         // remove all the layout content | ||||
|         this._layout.destroy_all_children(); | ||||
|  | ||||
|         for (let idx = 0; idx < mounts.length; idx++) { | ||||
|             let element = mounts[idx]; | ||||
|  | ||||
|             let actor = this._itemForMount(element.mount, element.apps); | ||||
|             this._layout.add(actor, { x_fill: true, | ||||
|                                       expand: true }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _itemForMount: function(mount, apps) { | ||||
|         let item = new St.BoxLayout(); | ||||
|  | ||||
|         // prepare the mount button content | ||||
|         let mountLayout = new St.BoxLayout(); | ||||
|  | ||||
|         let mountIcon = new St.Icon({ gicon: mount.get_icon(), | ||||
|                                       style_class: 'hotplug-resident-mount-icon' }); | ||||
|         mountLayout.add_actor(mountIcon); | ||||
|  | ||||
|         let labelBin = new St.Bin({ y_align: St.Align.MIDDLE }); | ||||
|         let mountLabel = | ||||
|             new St.Label({ text: mount.get_name(), | ||||
|                            style_class: 'hotplug-resident-mount-label', | ||||
|                            track_hover: true, | ||||
|                            reactive: true }); | ||||
|         labelBin.add_actor(mountLabel); | ||||
|         mountLayout.add_actor(labelBin); | ||||
|  | ||||
|         let mountButton = new St.Button({ child: mountLayout, | ||||
|                                           x_align: St.Align.START, | ||||
|                                           x_fill: true, | ||||
|                                           style_class: 'hotplug-resident-mount', | ||||
|                                           button_mask: St.ButtonMask.ONE }); | ||||
|         item.add(mountButton, { x_align: St.Align.START, | ||||
|                                 expand: true }); | ||||
|  | ||||
|         let ejectIcon =  | ||||
|             new St.Icon({ icon_name: 'media-eject-symbolic', | ||||
|                           style_class: 'hotplug-resident-eject-icon' }); | ||||
|  | ||||
|         let ejectButton = | ||||
|             new St.Button({ style_class: 'hotplug-resident-eject-button', | ||||
|                             button_mask: St.ButtonMask.ONE, | ||||
|                             child: ejectIcon }); | ||||
|         item.add(ejectButton, { x_align: St.Align.END }); | ||||
|  | ||||
|         // now connect signals | ||||
|         mountButton.connect('clicked', Lang.bind(this, function(actor, event) { | ||||
|             startAppForMount(apps[0], mount); | ||||
|         })); | ||||
|  | ||||
|         ejectButton.connect('clicked', Lang.bind(this, function() { | ||||
|             this._manager.ejectMount(mount); | ||||
|         })); | ||||
|  | ||||
|         return item; | ||||
|     }, | ||||
| }); | ||||
|  | ||||
| const AutorunTransientDispatcher = new Lang.Class({ | ||||
|     Name: 'AutorunTransientDispatcher', | ||||
|  | ||||
| @@ -559,12 +394,12 @@ const AutorunTransientNotification = new Lang.Class({ | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(manager, source) { | ||||
|         this.parent(source, source.title, null, { customContent: true }); | ||||
|         this.parent(source, source.title); | ||||
|  | ||||
|         this._manager = manager; | ||||
|         this._box = new St.BoxLayout({ style_class: 'hotplug-transient-box', | ||||
|                                        vertical: true }); | ||||
|         this.addActor(this._box); | ||||
|         this._bodyBin.child = this._box; | ||||
|  | ||||
|         this._mount = source.mount; | ||||
|  | ||||
|   | ||||
| @@ -102,15 +102,6 @@ const TelepathyClient = new Lang.Class({ | ||||
|         this._tpClient.set_handle_channels_func( | ||||
|             Lang.bind(this, this._handleChannels)); | ||||
|  | ||||
|         // Watch subscription requests and connection errors | ||||
|         this._subscriptionSource = null; | ||||
|         this._accountSource = null; | ||||
|  | ||||
|         // Workaround for gjs not supporting GPtrArray in signals. | ||||
|         // See BGO bug #653941 for context. | ||||
|         this._tpClient.set_contact_list_changed_func( | ||||
|             Lang.bind(this, this._contactListChanged)); | ||||
|  | ||||
|         // Allow other clients (such as Empathy) to pre-empt our channels if | ||||
|         // needed | ||||
|         this._tpClient.set_delegated_channels_callback( | ||||
| @@ -124,17 +115,12 @@ const TelepathyClient = new Lang.Class({ | ||||
|             throw new Error('Couldn\'t register Telepathy client. Error: \n' + e); | ||||
|         } | ||||
|  | ||||
|         this._accountManagerValidityChangedId = this._accountManager.connect('account-validity-changed', | ||||
|                                                                              Lang.bind(this, this._accountValidityChanged)); | ||||
|  | ||||
|         if (!this._accountManager.is_prepared(Tp.AccountManager.get_feature_quark_core())) | ||||
|             this._accountManager.prepare_async(null, Lang.bind(this, this._accountManagerPrepared)); | ||||
|     }, | ||||
|  | ||||
|     disable: function() { | ||||
|         this._tpClient.unregister(); | ||||
|         this._accountManager.disconnect(this._accountManagerValidityChangedId); | ||||
|         this._accountManagerValidityChangedId = 0; | ||||
|     }, | ||||
|  | ||||
|     _observeChannels: function(observer, account, conn, channels, | ||||
| @@ -219,33 +205,6 @@ const TelepathyClient = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _displayRoomInvitation: function(conn, channel, dispatchOp, context) { | ||||
|         // We can only approve the rooms if we have been invited to it | ||||
|         let selfContact = channel.group_get_self_contact(); | ||||
|         if (selfContact == null) { | ||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, | ||||
|                                         message: 'Not invited to the room' })); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let [invited, inviter, reason, msg] = channel.group_get_local_pending_contact_info(selfContact); | ||||
|         if (!invited) { | ||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, | ||||
|                                         message: 'Not invited to the room' })); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // FIXME: We don't have a 'chat room' icon (bgo #653737) use | ||||
|         // system-users for now as Empathy does. | ||||
|         let source = new ApproverSource(dispatchOp, _("Invitation"), | ||||
|                                         Gio.icon_new_for_string('system-users')); | ||||
|         Main.messageTray.add(source); | ||||
|  | ||||
|         let notif = new RoomInviteNotification(source, dispatchOp, channel, inviter); | ||||
|         source.notify(notif); | ||||
|         context.accept(); | ||||
|     }, | ||||
|  | ||||
|     _approveChannels: function(approver, account, conn, channels, | ||||
|                                dispatchOp, context) { | ||||
|         let channel = channels[0]; | ||||
| @@ -259,10 +218,6 @@ const TelepathyClient = new Lang.Class({ | ||||
|  | ||||
|         if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT) | ||||
|             this._approveTextChannel(account, conn, channel, dispatchOp, context); | ||||
|         else if (chanType == Tp.IFACE_CHANNEL_TYPE_CALL) | ||||
|             this._approveCall(account, conn, channel, dispatchOp, context); | ||||
|         else if (chanType == Tp.IFACE_CHANNEL_TYPE_FILE_TRANSFER) | ||||
|             this._approveFileTransfer(account, conn, channel, dispatchOp, context); | ||||
|         else | ||||
|             context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT, | ||||
|                                         message: 'Unsupported channel type' })); | ||||
| @@ -283,45 +238,9 @@ const TelepathyClient = new Lang.Class({ | ||||
|                 }})); | ||||
|  | ||||
|             context.accept(); | ||||
|         } else { | ||||
|             this._displayRoomInvitation(conn, channel, dispatchOp, context); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _approveCall: function(account, conn, channel, dispatchOp, context) { | ||||
|         let isVideo = false; | ||||
|  | ||||
|         let props = channel.borrow_immutable_properties(); | ||||
|  | ||||
|         if (props[Tp.PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO]) | ||||
|           isVideo = true; | ||||
|  | ||||
|         // We got the TpContact | ||||
|         let source = new ApproverSource(dispatchOp, _("Call"), isVideo ? | ||||
|                                         Gio.icon_new_for_string('camera-web') : | ||||
|                                         Gio.icon_new_for_string('audio-input-microphone')); | ||||
|         Main.messageTray.add(source); | ||||
|  | ||||
|         let notif = new AudioVideoNotification(source, dispatchOp, channel, | ||||
|             channel.get_target_contact(), isVideo); | ||||
|         source.notify(notif); | ||||
|         context.accept(); | ||||
|     }, | ||||
|  | ||||
|     _approveFileTransfer: function(account, conn, channel, dispatchOp, context) { | ||||
|         // Use the icon of the file being transferred | ||||
|         let gicon = Gio.content_type_get_icon(channel.get_mime_type()); | ||||
|  | ||||
|         // We got the TpContact | ||||
|         let source = new ApproverSource(dispatchOp, _("File Transfer"), gicon); | ||||
|         Main.messageTray.add(source); | ||||
|  | ||||
|         let notif = new FileTransferNotification(source, dispatchOp, channel, | ||||
|             channel.get_target_contact()); | ||||
|         source.notify(notif); | ||||
|         context.accept(); | ||||
|     }, | ||||
|  | ||||
|     _delegatedChannelsCb: function(client, channels) { | ||||
|         // Nothing to do as we don't make a distinction between observed and | ||||
|         // handled channels. | ||||
| @@ -329,105 +248,7 @@ const TelepathyClient = new Lang.Class({ | ||||
|  | ||||
|     _accountManagerPrepared: function(am, result) { | ||||
|         am.prepare_finish(result); | ||||
|  | ||||
|         let accounts = am.get_valid_accounts(); | ||||
|         for (let i = 0; i < accounts.length; i++) { | ||||
|             this._accountValidityChanged(am, accounts[i], true); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _accountValidityChanged: function(am, account, valid) { | ||||
|         if (!valid) | ||||
|             return; | ||||
|  | ||||
|         // It would be better to connect to "status-changed" but we cannot. | ||||
|         // See discussion in https://bugzilla.gnome.org/show_bug.cgi?id=654159 | ||||
|         account.connect("notify::connection-status", | ||||
|                         Lang.bind(this, this._accountConnectionStatusNotifyCb)); | ||||
|  | ||||
|         account.connect('notify::connection', | ||||
|                         Lang.bind(this, this._connectionChanged)); | ||||
|         this._connectionChanged(account); | ||||
|     }, | ||||
|  | ||||
|     _connectionChanged: function(account) { | ||||
|         let conn = account.get_connection(); | ||||
|         if (conn == null) | ||||
|             return; | ||||
|  | ||||
|         this._tpClient.grab_contact_list_changed(conn); | ||||
|         if (conn.get_contact_list_state() == Tp.ContactListState.SUCCESS) { | ||||
|             this._contactListChanged(conn, conn.dup_contact_list(), []); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _contactListChanged: function(conn, added, removed) { | ||||
|         for (let i = 0; i < added.length; i++) { | ||||
|             let contact = added[i]; | ||||
|  | ||||
|             contact.connect('subscription-states-changed', | ||||
|                             Lang.bind(this, this._subscriptionStateChanged)); | ||||
|             this._subscriptionStateChanged(contact); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _subscriptionStateChanged: function(contact) { | ||||
|         if (contact.get_publish_state() != Tp.SubscriptionState.ASK) | ||||
|             return; | ||||
|  | ||||
|         /* Implicitly accept publish requests if contact is already subscribed */ | ||||
|         if (contact.get_subscribe_state() == Tp.SubscriptionState.YES || | ||||
|             contact.get_subscribe_state() == Tp.SubscriptionState.ASK) { | ||||
|  | ||||
|             contact.authorize_publication_async(function(src, result) { | ||||
|                 src.authorize_publication_finish(result)}); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         /* Display notification to ask user to accept/reject request */ | ||||
|         let source = this._ensureAppSource(); | ||||
|  | ||||
|         let notif = new SubscriptionRequestNotification(source, contact); | ||||
|         source.notify(notif); | ||||
|     }, | ||||
|  | ||||
|     _accountConnectionStatusNotifyCb: function(account) { | ||||
|         let connectionError = account.connection_error; | ||||
|  | ||||
|         if (account.connection_status != Tp.ConnectionStatus.DISCONNECTED || | ||||
|             connectionError == Tp.error_get_dbus_name(Tp.Error.CANCELLED)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let notif = this._accountNotifications[account.get_object_path()]; | ||||
|         if (notif) | ||||
|             return; | ||||
|  | ||||
|         /* Display notification that account failed to connect */ | ||||
|         let source = this._ensureAppSource(); | ||||
|  | ||||
|         notif = new AccountNotification(source, account, connectionError); | ||||
|         this._accountNotifications[account.get_object_path()] = notif; | ||||
|         notif.connect('destroy', Lang.bind(this, function() { | ||||
|             delete this._accountNotifications[account.get_object_path()]; | ||||
|         })); | ||||
|         source.notify(notif); | ||||
|     }, | ||||
|  | ||||
|     _ensureAppSource: function() { | ||||
|         if (this._appSource == null) { | ||||
|             this._appSource = new MessageTray.Source(_("Chat"), 'empathy'); | ||||
|             this._appSource.policy = new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|  | ||||
|             Main.messageTray.add(this._appSource); | ||||
|             this._appSource.connect('destroy', Lang.bind(this, function () { | ||||
|                 this._appSource = null; | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         return this._appSource; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ChatSource = new Lang.Class({ | ||||
| @@ -545,7 +366,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|     _updateAvatarIcon: function() { | ||||
|         this.iconUpdated(); | ||||
|         this._notification.update(this._notification.title, null, { customContent: true }); | ||||
|         this._notification.update(this._notification.title); | ||||
|     }, | ||||
|  | ||||
|     open: function() { | ||||
| @@ -737,7 +558,7 @@ const ChatSource = new Lang.Class({ | ||||
|  | ||||
|         title = GLib.markup_escape_text(this.title, -1); | ||||
|  | ||||
|         this._notification.update(this._notification.title, null, { customContent: true, secondaryGIcon: this.getSecondaryIcon() }); | ||||
|         this._notification.update(this._notification.title, null, { secondaryGIcon: this.getSecondaryIcon() }); | ||||
|  | ||||
|         if (message) | ||||
|             msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>'; | ||||
| @@ -764,8 +585,7 @@ const ChatNotification = new Lang.Class({ | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source) { | ||||
|         this.parent(source, source.title, null, { customContent: true, secondaryGIcon: source.getSecondaryIcon() }); | ||||
|         this.setResident(true); | ||||
|         this.parent(source, source.title, null, { secondaryGIcon: source.getSecondaryIcon() }); | ||||
|  | ||||
|         this._responseEntry = new St.Entry({ style_class: 'chat-response', | ||||
|                                              can_focus: true }); | ||||
| @@ -781,15 +601,17 @@ const ChatNotification = new Lang.Class({ | ||||
|             this.emit('unfocused'); | ||||
|         })); | ||||
|  | ||||
|         this._createScrollArea(); | ||||
|         this._lastGroup = null; | ||||
|  | ||||
|         this._bodyBox = new St.BoxLayout({ style_class: 'chat-notification-body-box' }); | ||||
|         this._bodyBin.child = this._bodyBox; | ||||
|  | ||||
|         // 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.vscroll.adjustment.value; | ||||
|         this._scrollArea.add_style_class_name('chat-notification-scrollview'); | ||||
|         this._scrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) { | ||||
|         this._oldMaxScrollValue = this._bodyScrollArea.vscroll.adjustment.value; | ||||
|         this._bodyScrollArea.add_style_class_name('chat-notification-scrollview'); | ||||
|         this._bodyScrollArea.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) { | ||||
|             if (adjustment.value == this._oldMaxScrollValue) | ||||
|                 this.scrollTo(St.Side.BOTTOM); | ||||
|             this._oldMaxScrollValue = Math.max(adjustment.lower, adjustment.upper - adjustment.page_size); | ||||
| @@ -826,8 +648,7 @@ const ChatNotification = new Lang.Class({ | ||||
|         } | ||||
|  | ||||
|         if (message.direction == NotificationDirection.RECEIVED) { | ||||
|             this.update(this.source.title, messageBody, { customContent: true, | ||||
|                                                           bannerMarkup: true }); | ||||
|             this.update(this.source.title, messageBody, { bannerMarkup: true }); | ||||
|         } | ||||
|  | ||||
|         let group = (message.direction == NotificationDirection.RECEIVED ? | ||||
| @@ -864,7 +685,7 @@ const ChatNotification = new Lang.Class({ | ||||
|                 expired[i].actor.destroy(); | ||||
|         } | ||||
|  | ||||
|         let groups = this._contentArea.get_children(); | ||||
|         let groups = this._bodyBox.get_children(); | ||||
|         for (let i = 0; i < groups.length; i++) { | ||||
|             let group = groups[i]; | ||||
|             if (group.get_n_children() == 0) | ||||
| @@ -896,9 +717,9 @@ const ChatNotification = new Lang.Class({ | ||||
|         if (this._timestampTimeoutId) | ||||
|             Mainloop.source_remove(this._timestampTimeoutId); | ||||
|  | ||||
|         let highlighter = new MessageTray.URLHighlighter(props.body, | ||||
|                                                          true,  // line wrap? | ||||
|                                                          true); // allow markup? | ||||
|         let highlighter = new MessageTray.URLHighlighter(); | ||||
|         highlighter.actor.clutter_text.line_wrap = true; | ||||
|         highlighter.setMarkup(props.body, true); | ||||
|  | ||||
|         let body = highlighter.actor; | ||||
|  | ||||
| @@ -910,14 +731,16 @@ const ChatNotification = new Lang.Class({ | ||||
|         if (group != this._lastGroup) { | ||||
|             this._lastGroup = group; | ||||
|             let emptyLine = new St.Label({ style_class: 'chat-empty-line' }); | ||||
|             this.addActor(emptyLine); | ||||
|             this._bodyBox.add_child(emptyLine); | ||||
|         } | ||||
|  | ||||
|         this._lastMessageBox = new St.BoxLayout({ vertical: false }); | ||||
|         this._lastMessageBox.add(body, props.childProps); | ||||
|         this.addActor(this._lastMessageBox); | ||||
|  | ||||
|         this.updated(); | ||||
|         let revealer = new MessageTray.Revealer(body); | ||||
|         this._lastMessageBox.add(revealer, props.childProps); | ||||
|         revealer.show(true); | ||||
|  | ||||
|         this._bodyBox.add_child(this._lastMessageBox); | ||||
|  | ||||
|         let timestamp = props.timestamp; | ||||
|         this._history.unshift({ actor: body, time: timestamp, | ||||
| @@ -1052,7 +875,7 @@ const ChatNotification = new Lang.Class({ | ||||
|                                    group: 'meta', | ||||
|                                    styles: ['chat-meta-message'] }); | ||||
|  | ||||
|         this.update(newAlias, null, { customContent: true }); | ||||
|         this.update(newAlias); | ||||
|  | ||||
|         this._filterMessages(); | ||||
|     }, | ||||
| @@ -1105,359 +928,4 @@ const ChatNotification = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ApproverSource = new Lang.Class({ | ||||
|     Name: 'ApproverSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(dispatchOp, text, gicon) { | ||||
|         this._gicon = gicon; | ||||
|  | ||||
|         this.parent(text); | ||||
|  | ||||
|         this._dispatchOp = dispatchOp; | ||||
|  | ||||
|         // Destroy the source if the channel dispatch operation is invalidated | ||||
|         // as we can't approve any more. | ||||
|         this._invalidId = dispatchOp.connect('invalidated', | ||||
|                                              Lang.bind(this, function(domain, code, msg) { | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
|         return new MessageTray.NotificationApplicationPolicy('empathy'); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         if (this._invalidId != 0) { | ||||
|             this._dispatchOp.disconnect(this._invalidId); | ||||
|             this._invalidId = 0; | ||||
|         } | ||||
|  | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     getIcon: function() { | ||||
|         return this._gicon; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const RoomInviteNotification = new Lang.Class({ | ||||
|     Name: 'RoomInviteNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, dispatchOp, channel, inviter) { | ||||
|         this.parent(source, | ||||
|                     /* translators: argument is a room name like | ||||
|                      * room@jabber.org for example. */ | ||||
|                     _("Invitation to %s").format(channel.get_identifier()), | ||||
|                     null, | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         /* translators: first argument is the name of a contact and the second | ||||
|          * one the name of a room. "Alice is inviting you to join room@jabber.org | ||||
|          * for example. */ | ||||
|         this.addBody(_("%s is inviting you to join %s").format(inviter.get_alias(), channel.get_identifier())); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| // Audio Video | ||||
| const AudioVideoNotification = new Lang.Class({ | ||||
|     Name: 'AudioVideoNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, dispatchOp, channel, contact, isVideo) { | ||||
|         let title = ''; | ||||
|  | ||||
|         if (isVideo) | ||||
|              /* translators: argument is a contact name like Alice for example. */ | ||||
|             title = _("Video call from %s").format(contact.get_alias()); | ||||
|         else | ||||
|              /* translators: argument is a contact name like Alice for example. */ | ||||
|             title = _("Call from %s").format(contact.get_alias()); | ||||
|  | ||||
|         this.parent(source, title, null, { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         /* translators: this is a button label (verb), not a noun */ | ||||
|         this.addAction(_("Answer"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| // File Transfer | ||||
| const FileTransferNotification = new Lang.Class({ | ||||
|     Name: 'FileTransferNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, dispatchOp, channel, contact) { | ||||
|         this.parent(source, | ||||
|                     /* To translators: The first parameter is | ||||
|                      * the contact's alias and the second one is the | ||||
|                      * file name. The string will be something | ||||
|                      * like: "Alice is sending you test.ogg" | ||||
|                      */ | ||||
|                     _("%s is sending you %s").format(contact.get_alias(), | ||||
|                                                      channel.get_filename()), | ||||
|                     null, | ||||
|                     { customContent: true }); | ||||
|         this.setResident(true); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             dispatchOp.leave_channels_async(Tp.ChannelGroupChangeReason.NONE, '', function(src, result) { | ||||
|                 src.leave_channels_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             dispatchOp.handle_with_time_async('', global.get_current_time(), function(src, result) { | ||||
|                 src.handle_with_time_finish(result); | ||||
|             }); | ||||
|             this.destroy(); | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| // Subscription request | ||||
| const SubscriptionRequestNotification = new Lang.Class({ | ||||
|     Name: 'SubscriptionRequestNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, contact) { | ||||
|         this.parent(source, | ||||
|                     /* To translators: The parameter is the contact's alias */ | ||||
|                     _("%s would like permission to see when you are online").format(contact.get_alias()), | ||||
|                     null, { customContent: true }); | ||||
|  | ||||
|         this._contact = contact; | ||||
|         this._connection = contact.get_connection(); | ||||
|  | ||||
|         let layout = new St.BoxLayout({ vertical: false }); | ||||
|  | ||||
|         // Display avatar | ||||
|         let iconBox = new St.Bin({ style_class: 'avatar-box' }); | ||||
|         iconBox._size = 48; | ||||
|  | ||||
|         let textureCache = St.TextureCache.get_default(); | ||||
|         let file = contact.get_avatar_file(); | ||||
|  | ||||
|         if (file) { | ||||
|             let uri = file.get_uri(); | ||||
|             let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; | ||||
|             iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size, scaleFactor); | ||||
|         } | ||||
|         else { | ||||
|             iconBox.child = new St.Icon({ icon_name: 'avatar-default', | ||||
|                                           icon_size: iconBox._size }); | ||||
|         } | ||||
|  | ||||
|         layout.add(iconBox); | ||||
|  | ||||
|         // subscription request message | ||||
|         let label = new St.Label({ style_class: 'subscription-message', | ||||
|                                    text: contact.get_publish_request() }); | ||||
|  | ||||
|         layout.add(label); | ||||
|  | ||||
|         this.addActor(layout); | ||||
|  | ||||
|         this.addAction(_("Decline"), Lang.bind(this, function() { | ||||
|             contact.remove_async(function(src, result) { | ||||
|                 src.remove_finish(result); | ||||
|             }); | ||||
|         })); | ||||
|         this.addAction(_("Accept"), Lang.bind(this, function() { | ||||
|             // Authorize the contact and request to see his status as well | ||||
|             contact.authorize_publication_async(function(src, result) { | ||||
|                 src.authorize_publication_finish(result); | ||||
|             }); | ||||
|  | ||||
|             contact.request_subscription_async('', function(src, result) { | ||||
|                 src.request_subscription_finish(result); | ||||
|             }); | ||||
|         })); | ||||
|  | ||||
|         this._changedId = contact.connect('subscription-states-changed', | ||||
|             Lang.bind(this, this._subscriptionStatesChangedCb)); | ||||
|         this._invalidatedId = this._connection.connect('invalidated', | ||||
|             Lang.bind(this, this.destroy)); | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         if (this._changedId != 0) { | ||||
|             this._contact.disconnect(this._changedId); | ||||
|             this._changedId = 0; | ||||
|         } | ||||
|  | ||||
|         if (this._invalidatedId != 0) { | ||||
|             this._connection.disconnect(this._invalidatedId); | ||||
|             this._invalidatedId = 0; | ||||
|         } | ||||
|  | ||||
|         this.parent(); | ||||
|     }, | ||||
|  | ||||
|     _subscriptionStatesChangedCb: function(contact, subscribe, publish, msg) { | ||||
|         // Destroy the notification if the subscription request has been | ||||
|         // answered | ||||
|         if (publish != Tp.SubscriptionState.ASK) | ||||
|             this.destroy(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| // Messages from empathy/libempathy/empathy-utils.c | ||||
| // create_errors_to_message_hash() | ||||
|  | ||||
| /* Translator note: these should be the same messages that are | ||||
|  * used in Empathy, so just copy and paste from there. */ | ||||
| let _connectionErrorMessages = {}; | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.NETWORK_ERROR)] | ||||
|   = _("Network error"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.AUTHENTICATION_FAILED)] | ||||
|   = _("Authentication failed"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.ENCRYPTION_ERROR)] | ||||
|   = _("Encryption error"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_NOT_PROVIDED)] | ||||
|   = _("Certificate not provided"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_UNTRUSTED)] | ||||
|   = _("Certificate untrusted"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_EXPIRED)] | ||||
|   = _("Certificate expired"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_NOT_ACTIVATED)] | ||||
|   = _("Certificate not activated"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_HOSTNAME_MISMATCH)] | ||||
|   = _("Certificate hostname mismatch"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_FINGERPRINT_MISMATCH)] | ||||
|   = _("Certificate fingerprint mismatch"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_SELF_SIGNED)] | ||||
|   = _("Certificate self-signed"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CANCELLED)] | ||||
|   = _("Status is set to offline"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.ENCRYPTION_NOT_AVAILABLE)] | ||||
|   = _("Encryption is not available"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_INVALID)] | ||||
|   = _("Certificate is invalid"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_REFUSED)] | ||||
|   = _("Connection has been refused"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_FAILED)] | ||||
|   = _("Connection can't be established"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_LOST)] | ||||
|   = _("Connection has been lost"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.ALREADY_CONNECTED)] | ||||
|   = _("This account is already connected to the server"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CONNECTION_REPLACED)] | ||||
|   = _("Connection has been replaced by a new connection using the same resource"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.REGISTRATION_EXISTS)] | ||||
|   = _("The account already exists on the server"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.SERVICE_BUSY)] | ||||
|   = _("Server is currently too busy to handle the connection"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_REVOKED)] | ||||
|   = _("Certificate has been revoked"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_INSECURE)] | ||||
|   = _("Certificate uses an insecure cipher algorithm or is cryptographically weak"); | ||||
| _connectionErrorMessages[Tp.error_get_dbus_name(Tp.Error.CERT_LIMIT_EXCEEDED)] | ||||
|   = _("The length of the server certificate, or the depth of the server certificate chain, exceed the limits imposed by the cryptography library"); | ||||
| _connectionErrorMessages['org.freedesktop.DBus.Error.NoReply'] | ||||
|   = _("Internal error"); | ||||
|  | ||||
| const AccountNotification = new Lang.Class({ | ||||
|     Name: 'AccountNotification', | ||||
|     Extends: MessageTray.Notification, | ||||
|  | ||||
|     _init: function(source, account, connectionError) { | ||||
|         this.parent(source, | ||||
|                     /* translators: argument is the account name, like | ||||
|                      * name@jabber.org for example. */ | ||||
|                     _("Unable to connect to %s").format(account.get_display_name()), | ||||
|                     this._getMessage(connectionError)); | ||||
|  | ||||
|         this._account = account; | ||||
|  | ||||
|         this.addAction(_("View account"), Lang.bind(this, function() { | ||||
|             let cmd = 'empathy-accounts --select-account=' + | ||||
|                 account.get_path_suffix(); | ||||
|             let app_info = Gio.app_info_create_from_commandline(cmd, null, 0); | ||||
|             app_info.launch([], global.create_app_launch_context(0, -1)); | ||||
|         })); | ||||
|  | ||||
|         this._enabledId = account.connect('notify::enabled', | ||||
|                                           Lang.bind(this, function() { | ||||
|                                               if (!account.is_enabled()) | ||||
|                                                   this.destroy(); | ||||
|                                           })); | ||||
|  | ||||
|         this._invalidatedId = account.connect('invalidated', | ||||
|                                               Lang.bind(this, this.destroy)); | ||||
|  | ||||
|         this._connectionStatusId = account.connect('notify::connection-status', | ||||
|             Lang.bind(this, function() { | ||||
|                 let status = account.connection_status; | ||||
|                 if (status == Tp.ConnectionStatus.CONNECTED) { | ||||
|                     this.destroy(); | ||||
|                 } else if (status == Tp.ConnectionStatus.DISCONNECTED) { | ||||
|                     let connectionError = account.connection_error; | ||||
|  | ||||
|                     if (connectionError == Tp.error_get_dbus_name(Tp.Error.CANCELLED)) | ||||
|                         this.destroy(); | ||||
|                     else | ||||
|                         this.update(this.title, this._getMessage(connectionError)); | ||||
|                 } | ||||
|             })); | ||||
|     }, | ||||
|  | ||||
|     _getMessage: function(connectionError) { | ||||
|         let message; | ||||
|         if (connectionError in _connectionErrorMessages) { | ||||
|             message = _connectionErrorMessages[connectionError]; | ||||
|         } else { | ||||
|             message = _("Unknown reason"); | ||||
|         } | ||||
|         return message; | ||||
|     }, | ||||
|  | ||||
|     destroy: function() { | ||||
|         if (this._enabledId != 0) { | ||||
|             this._account.disconnect(this._enabledId); | ||||
|             this._enabledId = 0; | ||||
|         } | ||||
|  | ||||
|         if (this._invalidatedId != 0) { | ||||
|             this._account.disconnect(this._invalidatedId); | ||||
|             this._invalidatedId = 0; | ||||
|         } | ||||
|  | ||||
|         if (this._connectionStatusId != 0) { | ||||
|             this._account.disconnect(this._connectionStatusId); | ||||
|             this._connectionStatusId = 0; | ||||
|         } | ||||
|  | ||||
|         this.parent(); | ||||
|     } | ||||
| }); | ||||
| const Component = TelepathyClient; | ||||
|   | ||||
							
								
								
									
										169
									
								
								js/ui/layout.js
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								js/ui/layout.js
									
									
									
									
									
								
							| @@ -22,11 +22,6 @@ const KEYBOARD_ANIMATION_TIME = 0.15; | ||||
| const BACKGROUND_FADE_ANIMATION_TIME = 1.0; | ||||
| const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff); | ||||
|  | ||||
| // The message tray takes this much pressure | ||||
| // in the pressure barrier at once to release it. | ||||
| const MESSAGE_TRAY_PRESSURE_THRESHOLD = 250; // pixels | ||||
| const MESSAGE_TRAY_PRESSURE_TIMEOUT = 1000; // ms | ||||
|  | ||||
| const HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels | ||||
| const HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms | ||||
|  | ||||
| @@ -150,7 +145,6 @@ const LayoutManager = new Lang.Class({ | ||||
|  | ||||
|         this._keyboardIndex = -1; | ||||
|         this._rightPanelBarrier = null; | ||||
|         this._trayBarrier = null; | ||||
|  | ||||
|         this._inOverview = false; | ||||
|         this._updateRegionIdle = 0; | ||||
| @@ -210,7 +204,6 @@ const LayoutManager = new Lang.Class({ | ||||
|         this.trayBox = new St.Widget({ name: 'trayBox', | ||||
|                                        layout_manager: new Clutter.BinLayout() });  | ||||
|         this.addChrome(this.trayBox); | ||||
|         this._setupTrayPressure(); | ||||
|  | ||||
|         this.modalDialogGroup = new St.Widget({ name: 'modalDialogGroup', | ||||
|                                                 layout_manager: new Clutter.BinLayout() }); | ||||
| @@ -449,50 +442,9 @@ const LayoutManager = new Lang.Class({ | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _setupTrayPressure: function() { | ||||
|         this._trayPressure = new PressureBarrier(MESSAGE_TRAY_PRESSURE_THRESHOLD, | ||||
|                                                  MESSAGE_TRAY_PRESSURE_TIMEOUT, | ||||
|                                                  Shell.KeyBindingMode.NORMAL | | ||||
|                                                  Shell.KeyBindingMode.OVERVIEW); | ||||
|         this._trayPressure.setEventFilter(this._trayBarrierEventFilter); | ||||
|         this._trayPressure.connect('trigger', function(barrier) { | ||||
|             if (Main.layoutManager.bottomMonitor.inFullscreen) | ||||
|                 return; | ||||
|  | ||||
|             Main.messageTray.openTray(); | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     _updateTrayBarrier: function() { | ||||
|         let monitor = this.bottomMonitor; | ||||
|  | ||||
|         if (this._trayBarrier) { | ||||
|             this._trayPressure.removeBarrier(this._trayBarrier); | ||||
|             this._trayBarrier.destroy(); | ||||
|             this._trayBarrier = null; | ||||
|         } | ||||
|  | ||||
|         this._trayBarrier = new Meta.Barrier({ display: global.display, | ||||
|                                                x1: monitor.x, x2: monitor.x + monitor.width, | ||||
|                                                y1: monitor.y + monitor.height, y2: monitor.y + monitor.height, | ||||
|                                                directions: Meta.BarrierDirection.NEGATIVE_Y }); | ||||
|         this._trayPressure.addBarrier(this._trayBarrier); | ||||
|     }, | ||||
|  | ||||
|     _trayBarrierEventFilter: function(event) { | ||||
|         // Throw out all events where the pointer was grabbed by another | ||||
|         // client, as the client that grabbed the pointer expects to have | ||||
|         // complete control over it | ||||
|         if (event.grabbed && Main.modalCount == 0) | ||||
|             return true; | ||||
|  | ||||
|         return false; | ||||
|     }, | ||||
|  | ||||
|     _monitorsChanged: function() { | ||||
|         this._updateMonitors(); | ||||
|         this._updateBoxes(); | ||||
|         this._updateTrayBarrier(); | ||||
|         this._updateHotCorners(); | ||||
|         this._updateBackgrounds(); | ||||
|         this._updateFullscreen(); | ||||
| @@ -1096,10 +1048,10 @@ const HotCorner = new Lang.Class({ | ||||
|  | ||||
|         this._setupFallbackCornerIfNeeded(layoutManager); | ||||
|  | ||||
|         this._pressureBarrier = new PressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD, | ||||
|                                                     HOT_CORNER_PRESSURE_TIMEOUT, | ||||
|                                                     Shell.KeyBindingMode.NORMAL | | ||||
|                                                     Shell.KeyBindingMode.OVERVIEW); | ||||
|         this._pressureBarrier = new TriggerablePressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD, | ||||
|                                                                HOT_CORNER_PRESSURE_TIMEOUT, | ||||
|                                                                Shell.KeyBindingMode.NORMAL | | ||||
|                                                                Shell.KeyBindingMode.OVERVIEW); | ||||
|         this._pressureBarrier.connect('trigger', Lang.bind(this, this._toggleOverview)); | ||||
|  | ||||
|         // Cache the three ripples instead of dynamically creating and destroying them. | ||||
| @@ -1277,14 +1229,12 @@ const PressureBarrier = new Lang.Class({ | ||||
|     Name: 'PressureBarrier', | ||||
|  | ||||
|     _init: function(threshold, timeout, keybindingMode) { | ||||
|         this._threshold = threshold; | ||||
|         this._timeout = timeout; | ||||
|         this.threshold = threshold; | ||||
|         this.timeout = timeout; | ||||
|         this._keybindingMode = keybindingMode; | ||||
|         this._barriers = []; | ||||
|         this._eventFilter = null; | ||||
|  | ||||
|         this._isTriggered = false; | ||||
|         this._reset(); | ||||
|         this.reset(); | ||||
|     }, | ||||
|  | ||||
|     addBarrier: function(barrier) { | ||||
| @@ -1313,10 +1263,10 @@ const PressureBarrier = new Lang.Class({ | ||||
|         this._eventFilter = filter; | ||||
|     }, | ||||
|  | ||||
|     _reset: function() { | ||||
|     reset: function() { | ||||
|         this._barrierEvents = []; | ||||
|         this._currentPressure = 0; | ||||
|         this._lastTime = 0; | ||||
|         this.currentPressure = 0; | ||||
|     }, | ||||
|  | ||||
|     _isHorizontal: function(barrier) { | ||||
| @@ -1337,12 +1287,21 @@ const PressureBarrier = new Lang.Class({ | ||||
|             return Math.abs(event.dy); | ||||
|     }, | ||||
|  | ||||
|     get currentPressure() { | ||||
|         return this._currentPressure; | ||||
|     }, | ||||
|  | ||||
|     set currentPressure(value) { | ||||
|         this._currentPressure = value; | ||||
|         this.emit('pressure-changed'); | ||||
|     }, | ||||
|  | ||||
|     _trimBarrierEvents: function() { | ||||
|         // Events are guaranteed to be sorted in time order from | ||||
|         // oldest to newest, so just look for the first old event, | ||||
|         // and then chop events after that off. | ||||
|         let i = 0; | ||||
|         let threshold = this._lastTime - this._timeout; | ||||
|         let threshold = this._lastTime - this.timeout; | ||||
|  | ||||
|         while (i < this._barrierEvents.length) { | ||||
|             let [time, distance] = this._barrierEvents[i]; | ||||
| @@ -1355,21 +1314,75 @@ const PressureBarrier = new Lang.Class({ | ||||
|  | ||||
|         for (i = 0; i < firstNewEvent; i++) { | ||||
|             let [time, distance] = this._barrierEvents[i]; | ||||
|             this._currentPressure -= distance; | ||||
|             this.currentPressure = distance; | ||||
|         } | ||||
|  | ||||
|         this._barrierEvents = this._barrierEvents.slice(firstNewEvent); | ||||
|     }, | ||||
|  | ||||
|     _onBarrierLeft: function(barrier, event) { | ||||
|         this._reset(); | ||||
|         this.reset(); | ||||
|     }, | ||||
|  | ||||
|     _shouldUseEvent: function(barrier, event) { | ||||
|         if (this._eventFilter && this._eventFilter(event)) | ||||
|             return false; | ||||
|  | ||||
|         // Throw out all events not in the proper keybinding mode | ||||
|         if (!(this._keybindingMode & Main.keybindingMode)) | ||||
|             return false; | ||||
|  | ||||
|         let slide = this._getDistanceAlongBarrier(barrier, event); | ||||
|         let distance = this._getDistanceAcrossBarrier(barrier, event); | ||||
|  | ||||
|         // Throw out events where the cursor is move more | ||||
|         // along the axis of the barrier than moving with | ||||
|         // the barrier. | ||||
|         if (slide > distance) | ||||
|             return false; | ||||
|  | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _appendEvent: function(barrier, event) { | ||||
|         let distance = this._getDistanceAcrossBarrier(barrier, event); | ||||
|  | ||||
|         this._lastTime = event.time; | ||||
|  | ||||
|         this._trimBarrierEvents(); | ||||
|         distance = Math.min(15, distance); | ||||
|  | ||||
|         this._barrierEvents.push([event.time, distance]); | ||||
|         this.currentPressure += distance; | ||||
|     }, | ||||
|  | ||||
|     _onBarrierHit: function(barrier, event) { | ||||
|         if (!this._shouldUseEvent(barrier, event)) | ||||
|             return; | ||||
|  | ||||
|         this._appendEvent(barrier, event); | ||||
|     } | ||||
| }); | ||||
| Signals.addSignalMethods(PressureBarrier.prototype); | ||||
|  | ||||
| const TriggerablePressureBarrier = new Lang.Class({ | ||||
|     Name: 'TriggerablePressureBarrier', | ||||
|     Extends: PressureBarrier, | ||||
|  | ||||
|     _init: function(threshold, timeout, keybindingMode) { | ||||
|         this.parent(threshold, timeout, keybindingMode); | ||||
|         this._isTriggered = false; | ||||
|     }, | ||||
|  | ||||
|     _trigger: function() { | ||||
|         this._isTriggered = true; | ||||
|         this.emit('trigger'); | ||||
|         this._reset(); | ||||
|         this.reset(); | ||||
|     }, | ||||
|  | ||||
|     _onBarrierLeft: function() { | ||||
|         this.parent(); | ||||
|         this._isTriggered = false; | ||||
|     }, | ||||
|  | ||||
|     _onBarrierHit: function(barrier, event) { | ||||
| @@ -1378,37 +1391,17 @@ const PressureBarrier = new Lang.Class({ | ||||
|         if (this._isTriggered) | ||||
|             return; | ||||
|  | ||||
|         if (this._eventFilter && this._eventFilter(event)) | ||||
|         if (!this._shouldUseEvent(barrier, event)) | ||||
|             return; | ||||
|  | ||||
|         // Throw out all events not in the proper keybinding mode | ||||
|         if (!(this._keybindingMode & Main.keybindingMode)) | ||||
|             return; | ||||
|  | ||||
|         let slide = this._getDistanceAlongBarrier(barrier, event); | ||||
|         let distance = this._getDistanceAcrossBarrier(barrier, event); | ||||
|  | ||||
|         if (distance >= this._threshold) { | ||||
|         if (distance >= this.threshold) { | ||||
|             this._trigger(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Throw out events where the cursor is move more | ||||
|         // along the axis of the barrier than moving with | ||||
|         // the barrier. | ||||
|         if (slide > distance) | ||||
|             return; | ||||
|         this._appendEvent(barrier, event); | ||||
|  | ||||
|         this._lastTime = event.time; | ||||
|  | ||||
|         this._trimBarrierEvents(); | ||||
|         distance = Math.min(15, distance); | ||||
|  | ||||
|         this._barrierEvents.push([event.time, distance]); | ||||
|         this._currentPressure += distance; | ||||
|  | ||||
|         if (this._currentPressure >= this._threshold) | ||||
|         if (this.currentPressure >= this.threshold) | ||||
|             this._trigger(); | ||||
|     } | ||||
|     }, | ||||
| }); | ||||
| Signals.addSignalMethods(PressureBarrier.prototype); | ||||
|   | ||||
							
								
								
									
										1926
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
							
						
						
									
										1926
									
								
								js/ui/messageTray.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -91,21 +91,6 @@ const rewriteRules = { | ||||
|     ] | ||||
| }; | ||||
|  | ||||
| const STANDARD_TRAY_ICON_IMPLEMENTATIONS = { | ||||
|     'bluetooth-applet': 'bluetooth', | ||||
|     'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet | ||||
|                                              // when moved to control center | ||||
|     'gnome-sound-applet': 'volume', | ||||
|     'nm-applet': 'network', | ||||
|     'gnome-power-manager': 'battery', | ||||
|     'keyboard': 'keyboard', | ||||
|     'a11y-keyboard': 'a11y', | ||||
|     'kbd-scrolllock': 'keyboard', | ||||
|     'kbd-numlock': 'keyboard', | ||||
|     'kbd-capslock': 'keyboard', | ||||
|     'ibus-ui-gtk': 'keyboard' | ||||
| }; | ||||
|  | ||||
| const FdoNotificationDaemon = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemon', | ||||
|  | ||||
| @@ -120,16 +105,10 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|  | ||||
|         this._nextNotificationId = 1; | ||||
|  | ||||
|         this._trayManager = new Shell.TrayManager(); | ||||
|         this._trayIconAddedId = this._trayManager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded)); | ||||
|         this._trayIconRemovedId = this._trayManager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved)); | ||||
|  | ||||
|         Shell.WindowTracker.get_default().connect('notify::focus-app', | ||||
|             Lang.bind(this, this._onFocusAppChanged)); | ||||
|         Main.overview.connect('hidden', | ||||
|             Lang.bind(this, this._onFocusAppChanged)); | ||||
|  | ||||
|         this._trayManager.manage_screen(global.screen, Main.messageTray.actor); | ||||
|     }, | ||||
|  | ||||
|     _imageForNotificationData: function(hints) { | ||||
| @@ -170,28 +149,23 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     _lookupSource: function(title, pid, trayIcon) { | ||||
|     _lookupSource: function(title, pid) { | ||||
|         for (let i = 0; i < this._sources.length; i++) { | ||||
|             let source = this._sources[i]; | ||||
|             if (source.pid == pid && | ||||
|                 (source.initialTitle == title || source.trayIcon || trayIcon)) | ||||
|             if (source.pid == pid && source.initialTitle == title) | ||||
|                 return source; | ||||
|         } | ||||
|         return null; | ||||
|     }, | ||||
|  | ||||
|     // Returns the source associated with ndata.notification if it is set. | ||||
|     // If the existing or requested source is associated with a tray icon | ||||
|     // and passed in pid matches a pid of an existing source, the title | ||||
|     // match is ignored to enable representing a tray icon and notifications | ||||
|     // from the same application with a single source. | ||||
|     // | ||||
|     // If no existing source is found, a new source is created as long as | ||||
|     // pid is provided. | ||||
|     // | ||||
|     // Either a pid or ndata.notification is needed to retrieve or | ||||
|     // create a source. | ||||
|     _getSource: function(title, pid, ndata, sender, trayIcon) { | ||||
|     _getSource: function(title, pid, ndata, sender) { | ||||
|         if (!pid && !(ndata && ndata.notification)) | ||||
|             return null; | ||||
|  | ||||
| @@ -202,13 +176,13 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         if (ndata && ndata.notification) | ||||
|             return ndata.notification.source; | ||||
|  | ||||
|         let source = this._lookupSource(title, pid, trayIcon); | ||||
|         let source = this._lookupSource(title, pid); | ||||
|         if (source) { | ||||
|             source.setTitle(title); | ||||
|             return source; | ||||
|         } | ||||
|  | ||||
|         let source = new FdoNotificationDaemonSource(title, pid, sender, trayIcon, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|         let source = new FdoNotificationDaemonSource(title, pid, sender, ndata ? ndata.hints['desktop-entry'] : null); | ||||
|  | ||||
|         this._sources.push(source); | ||||
|         source.connect('destroy', Lang.bind(this, function() { | ||||
| @@ -334,13 +308,14 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     _makeButton: function(id, label, useActionIcons) { | ||||
|         let button = new St.Button({ can_focus: true }); | ||||
|         let button = new St.Button({ can_focus: true, | ||||
|                                      x_expand: true, | ||||
|                                      style_class: 'notification-button' }); | ||||
|         let iconName = id.endsWith('-symbolic') ? id : id + '-symbolic'; | ||||
|  | ||||
|         if (useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) { | ||||
|             button.add_style_class_name('notification-icon-button'); | ||||
|             button.child = new St.Icon({ icon_name: iconName }); | ||||
|             button.child = new St.Icon({ icon_name: iconName, icon_size: 16 }); | ||||
|         } else { | ||||
|             button.add_style_class_name('notification-button'); | ||||
|             button.label = label; | ||||
|         } | ||||
|         return button; | ||||
| @@ -379,8 +354,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         let gicon = this._iconForNotificationData(icon, hints); | ||||
|         let gimage = this._imageForNotificationData(hints); | ||||
|  | ||||
|         let image = null; | ||||
|  | ||||
|         // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon | ||||
|         // and don't show a large image. There are currently many applications that use | ||||
|         // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets | ||||
| @@ -389,10 +362,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         // So the logic here does the right thing for this case. If both an icon and either | ||||
|         // one of 'image-data' or 'image-path' are specified, we show both an icon and | ||||
|         // a large image. | ||||
|         if (gicon && gimage) | ||||
|             image = new St.Icon({ gicon: gimage, | ||||
|                                   icon_size: notification.IMAGE_SIZE }); | ||||
|         else if (!gicon && gimage) | ||||
|         if (!gicon && gimage) | ||||
|             gicon = gimage; | ||||
|         else if (!gicon) | ||||
|             gicon = this._fallbackIconForNotificationData(hints); | ||||
| @@ -402,7 +372,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                                              clear: true, | ||||
|                                              soundFile: hints['sound-file'], | ||||
|                                              soundName: hints['sound-name'] }); | ||||
|         notification.setImage(image); | ||||
|  | ||||
|         let hasDefaultAction = false; | ||||
|  | ||||
| @@ -442,7 +411,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|                 notification.setUrgency(MessageTray.Urgency.CRITICAL); | ||||
|                 break; | ||||
|         } | ||||
|         notification.setResident(hints.resident == true); | ||||
|         // 'transient' is a reserved keyword in JS, so we have to retrieve the value | ||||
|         // of the 'transient' hint with hints['transient'] rather than hints.transient | ||||
|         notification.setTransient(hints['transient'] == true); | ||||
| @@ -470,7 +438,6 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|             'body-markup', | ||||
|             // 'icon-multi', | ||||
|             'icon-static', | ||||
|             'persistence', | ||||
|             'sound', | ||||
|         ]; | ||||
|     }, | ||||
| @@ -492,7 +459,7 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         for (let i = 0; i < this._sources.length; i++) { | ||||
|             let source = this._sources[i]; | ||||
|             if (source.app == tracker.focus_app) { | ||||
|                 source.destroyNonResidentNotifications(); | ||||
|                 source.destroyNotifications(); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| @@ -507,30 +474,15 @@ const FdoNotificationDaemon = new Lang.Class({ | ||||
|         this._dbusImpl.emit_signal('ActionInvoked', | ||||
|                                    GLib.Variant.new('(us)', [id, action])); | ||||
|     }, | ||||
|  | ||||
|     _onTrayIconAdded: function(o, icon) { | ||||
|         let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : ''; | ||||
|         if (STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined) | ||||
|             return; | ||||
|  | ||||
|         let source = this._getSource(icon.title || icon.wm_class || C_("program", "Unknown"), icon.pid, null, null, icon); | ||||
|     }, | ||||
|  | ||||
|     _onTrayIconRemoved: function(o, icon) { | ||||
|         let source = this._lookupSource(null, icon.pid, true); | ||||
|         if (source) | ||||
|             source.destroy(); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|     Name: 'FdoNotificationDaemonSource', | ||||
|     Extends: MessageTray.Source, | ||||
|  | ||||
|     _init: function(title, pid, sender, trayIcon, appId) { | ||||
|     _init: function(title, pid, sender, appId) { | ||||
|         // Need to set the app before chaining up, so | ||||
|         // methods called from the parent constructor can find it | ||||
|         this.trayIcon = trayIcon; | ||||
|         this.pid = pid; | ||||
|         this.app = this._getApp(appId); | ||||
|  | ||||
| @@ -550,12 +502,6 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|                                                               Lang.bind(this, this._onNameVanished)); | ||||
|         else | ||||
|             this._nameWatcherId = 0; | ||||
|  | ||||
|         if (this.trayIcon) { | ||||
|             // Try again finding the app, using the WM_CLASS from the tray icon | ||||
|             this._setSummaryIcon(this.trayIcon); | ||||
|             this.useNotificationIcon = false; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     _createPolicy: function() { | ||||
| @@ -571,48 +517,23 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         // Destroy the notification source when its sender is removed from DBus. | ||||
|         // Only do so if this.app is set to avoid removing "notify-send" sources, senders | ||||
|         // of which аre removed from DBus immediately. | ||||
|         // Sender being removed from DBus would normally result in a tray icon being removed, | ||||
|         // so allow the code path that handles the tray icon being removed to handle that case. | ||||
|         if (!this.trayIcon && this.app) | ||||
|         if (this.app) | ||||
|             this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     processNotification: function(notification, gicon) { | ||||
|         if (gicon) | ||||
|             this._gicon = gicon; | ||||
|         if (!this.trayIcon) | ||||
|             this.iconUpdated(); | ||||
|  | ||||
|         this.iconUpdated(); | ||||
|  | ||||
|         let tracker = Shell.WindowTracker.get_default(); | ||||
|         if (notification.resident && this.app && tracker.focus_app == this.app) | ||||
|         if (this.app && tracker.focus_app == this.app) | ||||
|             this.pushNotification(notification); | ||||
|         else | ||||
|             this.notify(notification); | ||||
|     }, | ||||
|  | ||||
|     handleSummaryClick: function(button) { | ||||
|         if (!this.trayIcon) | ||||
|             return false; | ||||
|  | ||||
|         let event = Clutter.get_current_event(); | ||||
|  | ||||
|         // Left clicks are passed through only where there aren't unacknowledged | ||||
|         // notifications, so it possible to open them in summary mode; right | ||||
|         // clicks are always forwarded, as the right click menu is not useful for | ||||
|         // tray icons | ||||
|         if (button == 1 && | ||||
|             this.notifications.length > 0) | ||||
|             return false; | ||||
|  | ||||
|         let id = global.stage.connect('deactivate', Lang.bind(this, function () { | ||||
|             global.stage.disconnect(id); | ||||
|             this.trayIcon.click(event); | ||||
|         })); | ||||
|  | ||||
|         Main.overview.hide(); | ||||
|         return true; | ||||
|     }, | ||||
|  | ||||
|     _getApp: function(appId) { | ||||
|         let app; | ||||
|  | ||||
| @@ -620,16 +541,6 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|         if (app != null) | ||||
|             return app; | ||||
|  | ||||
|         if (this.trayIcon) { | ||||
|             app = Shell.AppSystem.get_default().lookup_startup_wmclass(this.trayIcon.wm_class); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|  | ||||
|             app = Shell.AppSystem.get_default().lookup_desktop_wmclass(this.trayIcon.wm_class); | ||||
|             if (app != null) | ||||
|                 return app; | ||||
|         } | ||||
|  | ||||
|         if (appId) { | ||||
|             app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop'); | ||||
|             if (app != null) | ||||
| @@ -651,12 +562,11 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|  | ||||
|     open: function() { | ||||
|         this.openApp(); | ||||
|         this.destroyNonResidentNotifications(); | ||||
|         this.destroyNotifications(); | ||||
|     }, | ||||
|  | ||||
|     _lastNotificationRemoved: function() { | ||||
|         if (!this.trayIcon) | ||||
|             this.destroy(); | ||||
|         this.destroy(); | ||||
|     }, | ||||
|  | ||||
|     openApp: function() { | ||||
| @@ -677,11 +587,7 @@ const FdoNotificationDaemonSource = new Lang.Class({ | ||||
|     }, | ||||
|  | ||||
|     createIcon: function(size) { | ||||
|         if (this.trayIcon) { | ||||
|             return new Clutter.Clone({ width: size, | ||||
|                                        height: size, | ||||
|                                        source: this.trayIcon }); | ||||
|         } else if (this.app) { | ||||
|         if (this.app) { | ||||
|             return this.app.create_icon_texture(size); | ||||
|         } else if (this._gicon) { | ||||
|             return new St.Icon({ gicon: this._gicon, | ||||
|   | ||||
| @@ -263,8 +263,6 @@ const Overview = new Lang.Class({ | ||||
|         this._overview.add(this._controls.actor, { y_fill: true, expand: true }); | ||||
|         this._controls.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); | ||||
|  | ||||
|         this._stack.add_actor(this._controls.indicatorActor); | ||||
|  | ||||
|         // TODO - recalculate everything when desktop size changes | ||||
|         this.dashIconSize = this._dash.iconSize; | ||||
|         this._dash.connect('icon-size-changed', | ||||
|   | ||||
| @@ -396,111 +396,6 @@ const DashSpacer = new Lang.Class({ | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const MessagesIndicator = new Lang.Class({ | ||||
|     Name: 'MessagesIndicator', | ||||
|  | ||||
|     _init: function(viewSelector) { | ||||
|         this._count = 0; | ||||
|         this._sources = []; | ||||
|         this._viewSelector = viewSelector; | ||||
|  | ||||
|         this._container = new St.BoxLayout({ style_class: 'messages-indicator-contents', | ||||
|                                              reactive: true, | ||||
|                                              track_hover: true, | ||||
|                                              x_expand: true, | ||||
|                                              y_expand: true, | ||||
|                                              x_align: Clutter.ActorAlign.CENTER }); | ||||
|  | ||||
|         this._icon = new St.Icon({ icon_name: 'user-idle-symbolic', | ||||
|                                    icon_size: 16 }); | ||||
|         this._container.add_actor(this._icon); | ||||
|  | ||||
|         this._label = new St.Label(); | ||||
|         this._container.add_actor(this._label); | ||||
|  | ||||
|         this._highlight = new St.Widget({ style_class: 'messages-indicator-highlight', | ||||
|                                           x_expand: true, | ||||
|                                           y_expand: true, | ||||
|                                           y_align: Clutter.ActorAlign.END, | ||||
|                                           visible: false }); | ||||
|  | ||||
|         this._container.connect('notify::hover', Lang.bind(this, | ||||
|             function() { | ||||
|                 this._highlight.visible = this._container.hover; | ||||
|             })); | ||||
|  | ||||
|         let clickAction = new Clutter.ClickAction(); | ||||
|         this._container.add_action(clickAction); | ||||
|         clickAction.connect('clicked', Lang.bind(this, | ||||
|             function() { | ||||
|                 Main.messageTray.openTray(); | ||||
|             })); | ||||
|  | ||||
|         Main.messageTray.connect('showing', Lang.bind(this, | ||||
|             function() { | ||||
|                 this._highlight.visible = false; | ||||
|                 this._container.hover = false; | ||||
|             })); | ||||
|  | ||||
|         let layout = new Clutter.BinLayout(); | ||||
|         this.actor = new St.Widget({ layout_manager: layout, | ||||
|                                      style_class: 'messages-indicator', | ||||
|                                      y_expand: true, | ||||
|                                      y_align: Clutter.ActorAlign.END, | ||||
|                                      visible: false }); | ||||
|         this.actor.add_actor(this._container); | ||||
|         this.actor.add_actor(this._highlight); | ||||
|  | ||||
|         Main.messageTray.connect('source-added', Lang.bind(this, this._onSourceAdded)); | ||||
|         Main.messageTray.connect('source-removed', Lang.bind(this, this._onSourceRemoved)); | ||||
|  | ||||
|         let sources = Main.messageTray.getSources(); | ||||
|         sources.forEach(Lang.bind(this, function(source) { this._onSourceAdded(null, source); })); | ||||
|  | ||||
|         this._viewSelector.connect('page-changed', Lang.bind(this, this._updateVisibility)); | ||||
|         Main.overview.connect('showing', Lang.bind(this, this._updateVisibility)); | ||||
|     }, | ||||
|  | ||||
|     _onSourceAdded: function(tray, source) { | ||||
|         if (source.trayIcon) | ||||
|             return; | ||||
|  | ||||
|         source.connect('count-updated', Lang.bind(this, this._updateCount)); | ||||
|         this._sources.push(source); | ||||
|         this._updateCount(); | ||||
|     }, | ||||
|  | ||||
|     _onSourceRemoved: function(tray, source) { | ||||
|         this._sources.splice(this._sources.indexOf(source), 1); | ||||
|         this._updateCount(); | ||||
|     }, | ||||
|  | ||||
|     _updateCount: function() { | ||||
|         let count = 0; | ||||
|         let hasChats = false; | ||||
|         this._sources.forEach(Lang.bind(this, | ||||
|             function(source) { | ||||
|                 count += source.indicatorCount; | ||||
|                 hasChats |= source.isChat; | ||||
|             })); | ||||
|  | ||||
|         this._count = count; | ||||
|         this._label.text = ngettext("%d new message", | ||||
|                                     "%d new messages", | ||||
|                                    count).format(count); | ||||
|  | ||||
|         this._icon.visible = hasChats; | ||||
|         this._updateVisibility(); | ||||
|     }, | ||||
|  | ||||
|     _updateVisibility: function() { | ||||
|         let activePage = this._viewSelector.getActivePage(); | ||||
|         let visible = ((this._count > 0) && (activePage == ViewSelector.ViewPage.WINDOWS)); | ||||
|  | ||||
|         this.actor.visible = visible; | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const ControlsLayout = new Lang.Class({ | ||||
|     Name: 'ControlsLayout', | ||||
|     Extends: Clutter.BinLayout, | ||||
| @@ -529,9 +424,6 @@ const ControlsManager = new Lang.Class({ | ||||
|         this.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility)); | ||||
|         this.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty)); | ||||
|  | ||||
|         this._indicator = new MessagesIndicator(this.viewSelector); | ||||
|         this.indicatorActor = this._indicator.actor; | ||||
|  | ||||
|         let layout = new ControlsLayout(); | ||||
|         this.actor = new St.Widget({ layout_manager: layout, | ||||
|                                      reactive: true, | ||||
|   | ||||
| @@ -21,10 +21,6 @@ struct _ShellTpClientPrivate | ||||
|   ShellTpClientHandleChannelsImpl handle_channels_impl; | ||||
|   gpointer user_data_handle_channels; | ||||
|   GDestroyNotify destroy_handle_channels; | ||||
|  | ||||
|   ShellTpClientContactListChangedImpl contact_list_changed_impl; | ||||
|   gpointer user_data_contact_list_changed; | ||||
|   GDestroyNotify destroy_contact_list_changed; | ||||
| }; | ||||
|  | ||||
| /** | ||||
| @@ -83,16 +79,6 @@ struct _ShellTpClientPrivate | ||||
|  * Signature of the implementation of the HandleChannels method. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * ShellTpClientContactListChangedImpl: | ||||
|  * @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared | ||||
|  * if possible | ||||
|  * @added: (element-type TelepathyGLib.Contact): a #GPtrArray of added #TpContact | ||||
|  * @removed: (element-type TelepathyGLib.Contact): a #GPtrArray of removed #TpContact | ||||
|  * | ||||
|  * Signature of the implementation of the ContactListChanged method. | ||||
|  */ | ||||
|  | ||||
| static void | ||||
| shell_tp_client_init (ShellTpClient *self) | ||||
| { | ||||
| @@ -226,13 +212,6 @@ shell_tp_client_dispose (GObject *object) | ||||
|       self->priv->user_data_handle_channels = NULL; | ||||
|     } | ||||
|  | ||||
|   if (self->priv->destroy_contact_list_changed != NULL) | ||||
|     { | ||||
|       self->priv->destroy_contact_list_changed (self->priv->user_data_contact_list_changed); | ||||
|       self->priv->destroy_contact_list_changed = NULL; | ||||
|       self->priv->user_data_contact_list_changed = NULL; | ||||
|     } | ||||
|  | ||||
|   if (dispose != NULL) | ||||
|     dispose (object); | ||||
| } | ||||
| @@ -290,40 +269,3 @@ shell_tp_client_set_handle_channels_func (ShellTpClient *self, | ||||
|   self->priv->user_data_handle_channels = user_data; | ||||
|   self->priv->destroy_handle_channels = destroy; | ||||
| } | ||||
|  | ||||
| void | ||||
| shell_tp_client_set_contact_list_changed_func (ShellTpClient *self, | ||||
|     ShellTpClientContactListChangedImpl contact_list_changed_impl, | ||||
|     gpointer user_data, | ||||
|     GDestroyNotify destroy) | ||||
| { | ||||
|   g_assert (self->priv->contact_list_changed_impl == NULL); | ||||
|  | ||||
|   self->priv->contact_list_changed_impl = contact_list_changed_impl; | ||||
|   self->priv->user_data_handle_channels = user_data; | ||||
|   self->priv->destroy_handle_channels = destroy; | ||||
| } | ||||
|  | ||||
| static void | ||||
| on_contact_list_changed (TpConnection *conn, | ||||
|                          GPtrArray *added, | ||||
|                          GPtrArray *removed, | ||||
|                          gpointer user_data) | ||||
| { | ||||
|   ShellTpClient *self = (ShellTpClient *) user_data; | ||||
|  | ||||
|   g_assert (self->priv->contact_list_changed_impl != NULL); | ||||
|  | ||||
|   self->priv->contact_list_changed_impl (conn, | ||||
|       added, removed, | ||||
|       self->priv->user_data_contact_list_changed); | ||||
| } | ||||
|  | ||||
| void | ||||
| shell_tp_client_grab_contact_list_changed (ShellTpClient *self, | ||||
|                                            TpConnection *conn) | ||||
| { | ||||
|   g_signal_connect (conn, "contact-list-changed", | ||||
|                     G_CALLBACK (on_contact_list_changed), | ||||
|                     self); | ||||
| } | ||||
|   | ||||
| @@ -86,19 +86,5 @@ void shell_tp_client_set_handle_channels_func (ShellTpClient *self, | ||||
|     gpointer user_data, | ||||
|     GDestroyNotify destroy); | ||||
|  | ||||
| typedef void (*ShellTpClientContactListChangedImpl) ( | ||||
|     TpConnection *connection, | ||||
|     GPtrArray *added, | ||||
|     GPtrArray *removed, | ||||
|     gpointer user_data); | ||||
|  | ||||
| void shell_tp_client_set_contact_list_changed_func (ShellTpClient *self, | ||||
|     ShellTpClientContactListChangedImpl contact_list_changed_impl, | ||||
|     gpointer user_data, | ||||
|     GDestroyNotify destroy); | ||||
|  | ||||
| void shell_tp_client_grab_contact_list_changed (ShellTpClient *self, | ||||
|     TpConnection *conn); | ||||
|  | ||||
| G_END_DECLS | ||||
| #endif /* __SHELL_TP_CLIENT_H__ */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user