dot/.local/share/gnome-shell/extensions/gsconnect@andyholmes.github.io/service/plugins/telephony.js

297 lines
8.6 KiB
JavaScript

'use strict';
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const PluginsBase = imports.service.plugins.base;
const TelephonyUI = imports.service.ui.telephony;
var Metadata = {
label: _('Telephony'),
id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Telephony',
incomingCapabilities: ['kdeconnect.telephony'],
outgoingCapabilities: [
'kdeconnect.telephony.request',
'kdeconnect.telephony.request_mute'
],
actions: {
legacyReply: {
// TRANSLATORS: Respond to an incoming call via SMS
label: _('Reply SMS'),
icon_name: 'sms-symbolic',
parameter_type: new GLib.VariantType('a{sv}'),
incoming: ['kdeconnect.telephony'],
outgoing: ['kdeconnect.sms.request']
},
muteCall: {
// TRANSLATORS: Silence the actively ringing call
label: _('Mute Call'),
icon_name: 'audio-volume-muted-symbolic',
parameter_type: null,
incoming: ['kdeconnect.telephony'],
outgoing: ['kdeconnect.telephony.request_mute']
}
}
};
/**
* Telephony Plugin
* https://github.com/KDE/kdeconnect-kde/tree/master/plugins/telephony
* https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/TelephonyPlugin
*/
var Plugin = GObject.registerClass({
GTypeName: 'GSConnectTelephonyPlugin'
}, class Plugin extends PluginsBase.Plugin {
_init(device) {
super._init(device, 'telephony');
}
get legacy_sms() {
// We have to do this lookup each time, because if we hold a reference
// to the plugin we don't know if it's disabled
let sms = this.device._plugins.get('sms');
return (sms && sms.settings.get_boolean('legacy-sms'));
}
async handlePacket(packet) {
try {
// This is the end of a 'ringing' or 'talking' event
if (packet.body.isCancel) {
let sender = packet.body.contactName || packet.body.phoneNumber;
this.device.hideNotification(`${packet.body.event}|${sender}`);
this._restoreMediaState();
// Only handle 'ringing' or 'talking' events, leave the notification
// plugin to handle 'missedCall' and 'sms' since they're repliable
} else if (['ringing', 'talking'].includes(packet.body.event)) {
this._handleEvent(packet);
// Legacy messaging support
} else if (packet.body.event === 'sms' && this.legacy_sms) {
this._handleLegacyMessage(packet);
}
} catch (e) {
logError(e);
}
}
/**
* Change volume, microphone and media player state in response to an
* incoming or answered call.
*
* @param {String} eventType - 'ringing' or 'talking'
*/
_setMediaState(eventType) {
// Mixer Volume
let pulseaudio = this.service.components.get('pulseaudio');
if (pulseaudio) {
switch (this.settings.get_string(`${eventType}-volume`)) {
case 'restore':
pulseaudio.restore();
break;
case 'lower':
pulseaudio.lowerVolume();
break;
case 'mute':
pulseaudio.muteVolume();
break;
}
if (eventType === 'talking' && this.settings.get_boolean('talking-microphone')) {
pulseaudio.muteMicrophone();
}
}
// Media Playback
let mpris = this.service.components.get('mpris');
if (mpris && this.settings.get_boolean(`${eventType}-pause`)) {
mpris.pauseAll();
}
}
/**
* Restore volume, microphone and media player state (if changed), making
* sure to unpause before raising volume.
*/
_restoreMediaState() {
// Media Playback
let mpris = this.service.components.get('mpris');
if (mpris) {
mpris.unpauseAll();
}
// Mixer Volume
let pulseaudio = this.service.components.get('pulseaudio');
if (pulseaudio) {
pulseaudio.restore();
}
}
/**
* Load a Gdk.Pixbuf from base64 encoded data
*
* @param {string} data - Base64 encoded JPEG data
*/
_getThumbnailPixbuf(data) {
let loader;
try {
data = GLib.base64_decode(data);
loader = new GdkPixbuf.PixbufLoader();
loader.write(data);
loader.close();
} catch (e) {
debug(e);
}
return loader.get_pixbuf();
}
/**
* Show a local notification, possibly with actions
*
* @param {object} packet - A telephony packet for this event
*/
_handleEvent(packet) {
let body;
let buttons = [];
let icon = new Gio.ThemedIcon({name: 'call-start-symbolic'});
let priority = Gio.NotificationPriority.NORMAL;
// Ensure we have a sender
// TRANSLATORS: No name or phone number
let sender = _('Unknown Contact');
if (packet.body.contactName) {
sender = packet.body.contactName;
} else if (packet.body.phoneNumber) {
sender = packet.body.phoneNumber;
}
// If there's a photo, use it as the notification icon
if (packet.body.phoneThumbnail) {
icon = this._getThumbnailPixbuf(packet.body.phoneThumbnail);
}
if (packet.body.event === 'ringing') {
this._setMediaState('ringing');
// TRANSLATORS: The phone is ringing
body = _('Incoming call');
buttons = [{
action: 'muteCall',
// TRANSLATORS: Silence the actively ringing call
label: _('Mute'),
parameter: null
}];
priority = Gio.NotificationPriority.URGENT;
}
if (packet.body.event === 'talking') {
this.device.hideNotification(`ringing|${sender}`);
this._setMediaState('talking');
// TRANSLATORS: A phone call is active
body = _('Ongoing call');
}
this.device.showNotification({
id: `${packet.body.event}|${sender}`,
title: sender,
body: body,
icon: icon,
priority: priority,
buttons: buttons
});
}
_handleLegacyMessage(packet) {
let action = null;
let icon = new Gio.ThemedIcon({name: 'sms-symbolic'});
// Ensure we have a sender
// TRANSLATORS: No name or phone number
let sender = _('Unknown Contact');
if (packet.body.contactName) {
sender = packet.body.contactName;
} else if (packet.body.phoneNumber) {
sender = packet.body.phoneNumber;
}
// If there's a photo, use it as the notification icon
if (packet.body.phoneThumbnail) {
icon = this._getThumbnailPixbuf(packet.body.phoneThumbnail);
}
// If there's a phone number we can make this repliable
if (packet.body.phoneNumber) {
action = {
name: 'legacyReply',
parameter: GLib.Variant.full_pack(packet)
};
}
// Show notification
this.device.showNotification({
id: `${packet.body.event}|${sender}`,
title: sender,
body: packet.body.messageBody,
icon: icon,
priority: Gio.NotificationPriority.NORMAL,
action: action
});
}
legacyReply(packet) {
try {
let plugin = this.device._plugins.get('sms');
if (plugin === undefined) {
throw new Error('SMS Plugin is disabled');
}
let dialog = new TelephonyUI.LegacyMessagingDialog({
device: this.device,
message: {
date: packet.id,
addresses: [{address: packet.body.phoneNumber}],
body: packet.body.messageBody,
sender: packet.body.contactName || _('Unknown Contact'),
type: 1 // MessageBox.INBOX
},
plugin: plugin
});
dialog.present();
} catch (e) {
logError(e);
}
}
/**
* Silence an incoming call
*/
muteCall() {
this.device.sendPacket({
type: 'kdeconnect.telephony.request_mute',
body: {}
});
this._restoreMediaState();
}
});