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

199 lines
5.3 KiB
JavaScript

'use strict';
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const PluginsBase = imports.service.plugins.base;
var Metadata = {
label: _('System Volume'),
id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.SystemVolume',
incomingCapabilities: ['kdeconnect.systemvolume.request'],
outgoingCapabilities: ['kdeconnect.systemvolume'],
actions: {}
};
/**
* SystemVolume Plugin
* https://github.com/KDE/kdeconnect-kde/tree/master/plugins/systemvolume
* https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/SystemvolumePlugin/
*/
var Plugin = GObject.registerClass({
GTypeName: 'GSConnectSystemVolumePlugin'
}, class Plugin extends PluginsBase.Plugin {
_init(device) {
super._init(device, 'systemvolume');
try {
// Connect to the mixer
this._pulseaudio = this.service.components.get('pulseaudio');
this._streamChangedId = this._pulseaudio.connect(
'stream-changed',
this._sendSink.bind(this)
);
this._outputAddedId = this._pulseaudio.connect(
'output-added',
this._sendSinkList.bind(this)
);
this._outputRemovedId = this._pulseaudio.connect(
'output-removed',
this._sendSinkList.bind(this)
);
// Cache stream properties
this._cache = new WeakMap();
} catch (e) {
this.destroy();
e.name = 'GvcError';
throw e;
}
}
handlePacket(packet) {
switch (true) {
case packet.body.hasOwnProperty('requestSinks'):
this._sendSinkList();
break;
case packet.body.hasOwnProperty('name'):
this._changeSink(packet);
break;
}
}
connected() {
super.connected();
this._sendSinkList();
}
/**
* Handle a request to change an output
*/
_changeSink(packet) {
let stream;
for (let sink of this._pulseaudio.get_sinks()) {
if (sink.name === packet.body.name) {
stream = sink;
break;
}
}
// No sink with the given name
if (stream === undefined) {
this._sendSinkList();
return;
}
// Get a cache and store volume and mute states if changed
let cache = this._cache.get(stream) || {};
if (packet.body.hasOwnProperty('muted')) {
cache.muted = packet.body.muted;
this._cache.set(stream, cache);
stream.change_is_muted(packet.body.muted);
}
if (packet.body.hasOwnProperty('volume')) {
cache.volume = packet.body.volume;
this._cache.set(stream, cache);
stream.volume = packet.body.volume;
stream.push_volume();
}
}
/**
* Update the cache for @stream
*
* @param {Gvc.MixerStream} stream - The stream to cache
* @return {object} - The updated cache object
*/
_updateCache(stream) {
let state = {
name: stream.name,
description: stream.display_name,
muted: stream.is_muted,
volume: stream.volume,
maxVolume: this._pulseaudio.get_vol_max_norm()
};
this._cache.set(stream, state);
return state;
}
/**
* Send the state of a local sink
*
* @param {Gvc.MixerControl} mixer - The mixer that owns the stream
* @param {Number} id - The Id of the stream that changed
*/
_sendSink(mixer, id) {
// Avoid starving the packet channel when fading
if (this._pulseaudio.fading) {
return;
}
// Check the cache
let stream = this._pulseaudio.lookup_stream_id(id);
let cache = this._cache.get(stream) || {};
// If the port has changed we have to send the whole list to update the
// display name
if (!cache.display_name || cache.display_name !== stream.display_name) {
this._sendSinkList();
return;
}
// If only volume and/or mute are set, send a single update
if (cache.volume !== stream.volume || cache.muted !== stream.is_muted) {
// Update the cache
let state = this._updateCache(stream);
// Send the stream update
this.device.sendPacket({
type: 'kdeconnect.systemvolume',
body: state
});
}
}
/**
* Send a list of local sinks
*/
_sendSinkList() {
let sinkList = this._pulseaudio.get_sinks().map(sink => {
return this._updateCache(sink);
});
// Send the sinkList
this.device.sendPacket({
type: 'kdeconnect.systemvolume',
body: {
sinkList: sinkList
}
});
}
destroy() {
try {
this._pulseaudio.disconnect(this._streamChangedId);
this._pulseaudio.disconnect(this._outputAddedId);
this._pulseaudio.disconnect(this._outputRemovedId);
} catch (e) {
debug(e, this.device.name);
}
super.destroy();
}
});