199 lines
5.3 KiB
JavaScript
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();
|
||
|
}
|
||
|
});
|
||
|
|