Compare commits
21 Commits
citadel
...
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__ */
|
||||
|
Loading…
Reference in New Issue
Block a user