Files
clapper/src/prefs.js
Rafał Dzięgiel 79618edd1e Port to new gtuber lib
Current YouTube code was broken for quite some time. Replace it with
the new Gtuber lib to make this code separate, independent and easier to maintain.
2021-10-18 15:28:34 +02:00

555 lines
13 KiB
JavaScript

const { Adw, GObject, Gio, Gst, Gtk } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const Gtuber = imports.src.gtuber;
const { debug } = Debug;
const { settings } = Misc;
/* PlayFlags are not exported through GI */
Gst.PlayFlags = {
VIDEO: 1,
AUDIO: 2,
TEXT: 4,
VIS: 8,
SOFT_VOLUME: 16,
NATIVE_AUDIO: 32,
NATIVE_VIDEO: 64,
DOWNLOAD: 128,
BUFFERING: 256,
DEINTERLACE: 512,
SOFT_COLORBALANCE: 1024,
FORCE_FILTERS: 2048,
FORCE_SW_DECODERS: 4096,
};
const widgetOpts = {
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
};
function getCommonProps()
{
return {
'schema-name': GObject.ParamSpec.string(
'schema-name',
'GSchema setting name',
'Name of the setting to bind',
GObject.ParamFlags.WRITABLE,
null
),
};
}
const flags = Gio.SettingsBindFlags.DEFAULT;
let PrefsActionRow = GObject.registerClass({
GTypeName: 'ClapperPrefsActionRow',
Properties: getCommonProps(),
},
class ClapperPrefsActionRow extends Adw.ActionRow
{
_init(widget)
{
super._init();
this._schemaName = null;
this._bindProp = null;
if(widget) {
this.add_suffix(widget);
this.set_activatable_widget(widget);
}
}
set schema_name(value)
{
this._schemaName = value;
}
vfunc_realize()
{
super.vfunc_realize();
if(this._schemaName && this._bindProp) {
settings.bind(this._schemaName,
this.activatable_widget, this._bindProp, flags
);
}
this._schemaName = null;
}
});
let PrefsSubpageRow = GObject.registerClass({
GTypeName: 'ClapperPrefsSubpageRow',
Properties: getCommonProps(),
},
class ClapperPrefsSubpageRow extends Adw.ActionRow
{
_init(widget)
{
super._init({
activatable: true,
});
this._prefsSubpage = null;
const icon = new Gtk.Image({
icon_name: 'go-next-symbolic',
});
this.add_suffix(icon);
}
vfunc_activate()
{
super.vfunc_activate();
if(!this._prefsSubpage)
this._prefsSubpage = this._createSubpage();
const prefs = this.get_ancestor(PrefsWindow);
prefs.present_subpage(this._prefsSubpage);
}
_createSubpage()
{
/* For override */
return null;
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsSwitch',
Properties: {
'custom-icon-name': GObject.ParamSpec.string(
'custom-icon-name',
'Icon name',
'Name of the icon',
GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT_ONLY,
null
),
'custom-icon-subtitle': GObject.ParamSpec.string(
'custom-icon-subtitle',
'Icon subtitle',
'Text below the icon',
GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT_ONLY,
null
),
}
},
class ClapperPrefsSwitch extends PrefsActionRow
{
_init(opts)
{
super._init(null);
this._bindProp = 'active';
if(opts.custom_icon_name || opts.custom_icon_subtitle) {
const box = new Gtk.Box({
margin_top: 2,
orientation: Gtk.Orientation.VERTICAL,
valign: Gtk.Align.CENTER,
});
const customIcon = new Gtk.Image({
icon_name: opts.custom_icon_name || null,
});
box.append(customIcon);
const customLabel = new Gtk.Label({
label: opts.custom_icon_subtitle || '',
});
customLabel.add_css_class('subtitle');
box.append(customLabel);
this.add_suffix(box);
}
const sw = new Gtk.Switch(widgetOpts);
this.add_suffix(sw);
this.set_activatable_widget(sw);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsPlayFlagSwitch',
Properties: {
'play-flag': GObject.ParamSpec.int(
'play-flag',
'PlayFlag',
'Value of the gstreamer play flag to toggle',
GObject.ParamFlags.WRITABLE,
1, 4096, 1,
),
},
},
class ClapperPrefsPlayFlagSwitch extends PrefsActionRow
{
_init()
{
super._init(new Gtk.Switch(widgetOpts));
this._flag = 1;
this._doneRealize = false;
}
set play_flag(value)
{
this._flag = value;
}
vfunc_realize()
{
super.vfunc_realize();
if(!this._doneRealize) {
const playFlags = settings.get_int('play-flags');
this.activatable_widget.active = (
(playFlags & this._flag) === this._flag
);
this.activatable_widget.connect(
'notify::active', this._onPlayFlagToggled.bind(this)
);
}
this._doneRealize = true;
}
_onPlayFlagToggled()
{
let playFlags = settings.get_int('play-flags');
if(this.activatable_widget.active)
playFlags |= this._flag;
else
playFlags &= ~this._flag;
settings.set_int('play-flags', playFlags);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsSpin',
Properties: {
'spin-adjustment': GObject.ParamSpec.object(
'spin-adjustment',
'GtkAdjustment',
'Custom GtkAdjustment for spin button',
GObject.ParamFlags.WRITABLE,
Gtk.Adjustment
),
},
},
class ClapperPrefsSpin extends PrefsActionRow
{
_init()
{
super._init(new Gtk.SpinButton(widgetOpts));
this._bindProp = 'value';
}
set spin_adjustment(value)
{
this.activatable_widget.set_adjustment(value);
}
});
let PrefsPluginFeature = GObject.registerClass({
GTypeName: 'ClapperPrefsPluginFeature',
},
class ClapperPrefsPluginFeature extends Adw.ActionRow
{
_init(featureObj)
{
super._init({
title: featureObj.name,
});
const enableSwitch = new Gtk.Switch(widgetOpts);
const spinButton = new Gtk.SpinButton(widgetOpts);
spinButton.set_range(0, 512);
spinButton.set_increments(1, 1);
enableSwitch.active = featureObj.enabled;
spinButton.value = featureObj.rank;
this.currentRank = featureObj.rank;
this.add_suffix(enableSwitch);
this.add_suffix(spinButton);
enableSwitch.bind_property('active', spinButton, 'sensitive',
GObject.BindingFlags.SYNC_CREATE
);
enableSwitch.connect('notify::active', this._onSwitchActivate.bind(this));
spinButton.connect('value-changed', this._onValueChanged.bind(this));
}
_updateRanking(data)
{
settings.set_string('plugin-ranking', JSON.stringify(data));
}
_onSwitchActivate(enableSwitch)
{
const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage);
const pluginExp = this.get_ancestor(PrefsPluginExpander);
if(enableSwitch.active) {
settingsData[this.title] = this.currentRank;
pluginExp.modCount++;
}
else if(settingsData[this.title] != null) {
delete settingsData[this.title];
pluginExp.modCount--;
}
this._updateRanking(settingsData);
}
_onValueChanged(spinButton)
{
const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage);
this.currentRank = spinButton.value;
settingsData[this.title] = this.currentRank;
this._updateRanking(settingsData);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsFont',
},
class ClapperPrefsFont extends PrefsActionRow
{
_init()
{
const opts = {
use_font: true,
use_size: true,
};
Object.assign(opts, widgetOpts);
super._init(new Gtk.FontButton(opts));
this._bindProp = 'font';
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsCombo',
Properties: getCommonProps(),
},
class ClapperPrefsCombo extends Adw.ComboRow
{
_init()
{
super._init();
this._schemaName = null;
}
set schema_name(value)
{
this._schemaName = value;
}
vfunc_realize()
{
super.vfunc_realize();
if(this._schemaName)
settings.bind(this._schemaName, this, 'selected', flags);
this._schemaName = null;
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsExpander',
Properties: getCommonProps(),
},
class ClapperPrefsExpander extends Adw.ExpanderRow
{
_init()
{
super._init({
show_enable_switch: true,
});
}
set schema_name(value)
{
settings.bind(value, this, 'enable-expansion', flags);
}
});
GObject.registerClass({
GTypeName: 'ClapperPrefsPluginRankingSubpageRow',
},
class ClapperPrefsPluginRankingSubpageRow extends PrefsSubpageRow
{
_createSubpage()
{
return new PrefsPluginRankingSubpage();
}
});
let PrefsPluginExpander = GObject.registerClass({
GTypeName: 'ClapperPrefsPluginExpander',
},
class ClapperPrefsPluginExpander extends Adw.ExpanderRow
{
_init(plugin, modCount)
{
super._init({
title: plugin,
show_enable_switch: false,
});
this.modCount = modCount;
this.expandSignal = this.connect(
'notify::expanded', this._onExpandedNotify.bind(this)
);
}
set modCount(value)
{
this._modCount = value;
this.icon_name = (value > 0) ? 'dialog-information-symbolic' : null;
debug(`Plugin ${this.title} has ${value} modified features`);
}
get modCount()
{
return this._modCount;
}
_onExpandedNotify()
{
if(!this.expanded)
return;
this.disconnect(this.expandSignal);
this.expandSignal = null;
const { pluginsData } = this.get_ancestor(PrefsPluginRankingSubpage);
pluginsData[this.title].sort((a, b) =>
(a.name > b.name) - (a.name < b.name)
);
const featuresNames = Object.keys(pluginsData[this.title]);
debug(`Adding ${featuresNames.length} features to the list of plugin: ${this.title}`);
for(let featureObj of pluginsData[this.title])
this.add(new PrefsPluginFeature(featureObj));
}
});
let PrefsPluginRankingSubpage = GObject.registerClass({
GTypeName: 'ClapperPrefsPluginRankingSubpage',
Template: Misc.getResourceUri('ui/preferences-plugin-ranking-subpage.ui'),
InternalChildren: ['decoders_group'],
},
class ClapperPrefsPluginRankingSubpage extends Gtk.Box
{
_init()
{
super._init();
if(!Gst.is_initialized())
Gst.init(null);
const gstRegistry = Gst.Registry.get();
const decoders = gstRegistry.feature_filter(this._decodersFilterCb, false);
const plugins = {};
const mods = {};
this.settingsData = {};
/* In case someone messed up gsettings values */
try {
this.settingsData = JSON.parse(settings.get_string('plugin-ranking'));
/* Might be an array in older Clapper versions */
if(Array.isArray(this.settingsData))
this.settingsData = {};
}
catch(err) { /* Ignore */ }
for(let decoder of decoders) {
const pluginName = decoder.get_plugin_name();
/* Do not add unsupported plugins */
switch(pluginName) {
case 'playback':
continue;
default:
break;
}
if(!plugins[pluginName])
plugins[pluginName] = [];
const decName = decoder.get_name();
const isModified = (this.settingsData[decName] != null);
plugins[pluginName].push({
name: decName,
rank: decoder.get_rank(),
enabled: isModified,
});
if(isModified) {
if(!mods[pluginName])
mods[pluginName] = 0;
mods[pluginName]++;
}
}
const pluginsNames = Object.keys(plugins);
debug(`Adding ${pluginsNames.length} found plugins to the list`);
this.pluginsData = pluginsNames.sort().reduce((res, key) =>
(res[key] = plugins[key], res), {}
);
for(let plugin in this.pluginsData) {
const modCount = mods[plugin] || 0;
this._decoders_group.add(new PrefsPluginExpander(plugin, modCount));
}
}
_decodersFilterCb(feature)
{
return (
feature.list_is_type
&& feature.list_is_type(Gst.ELEMENT_FACTORY_TYPE_DECODER)
);
}
_onReturnClicked(button)
{
const prefs = this.get_ancestor(PrefsWindow);
prefs.close_subpage();
}
});
var PrefsWindow = GObject.registerClass({
GTypeName: 'ClapperPrefsWindow',
Template: Misc.getResourceUri('ui/preferences-window.ui'),
InternalChildren: ['gtuber_group'],
},
class ClapperPrefsWindow extends Adw.PreferencesWindow
{
_init(window)
{
super._init({
transient_for: window,
});
this._gtuber_group.visible = Gtuber.isAvailable;
this.show();
}
});