From c8a5277908a9b01cb6f022eec5eaf31447882909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Sat, 10 Jul 2021 08:50:01 +0200 Subject: [PATCH] Port preferences window to libadwaita --- data/com.github.rafostar.Clapper.gschema.xml | 38 +- src/actions.js | 3 +- src/controls.js | 2 +- src/dialogs.js | 84 -- src/player.js | 66 +- src/prefs.js | 763 +++++++++++-------- src/prefsBase.js | 238 ------ src/widget.js | 2 +- src/youtube.js | 2 +- src/youtubeItags.js | 5 + ui/preferences-plugin-ranking-subpage.ui | 30 + ui/preferences-window.ui | 292 +++++++ 12 files changed, 822 insertions(+), 703 deletions(-) delete mode 100644 src/prefsBase.js create mode 100644 ui/preferences-plugin-ranking-subpage.ui create mode 100644 ui/preferences-window.ui diff --git a/data/com.github.rafostar.Clapper.gschema.xml b/data/com.github.rafostar.Clapper.gschema.xml index ed1c57e2..f43b58f4 100644 --- a/data/com.github.rafostar.Clapper.gschema.xml +++ b/data/com.github.rafostar.Clapper.gschema.xml @@ -6,35 +6,31 @@ false Automatically enter fullscreen when first file is loaded - - "restore" - Mode used for startup volume value + + false + Set custom volume value at startup 100 Custom initial volume value in percentage after startup - - false - Keep showing last video frame after playback finishes - - - false - Automatically close the app after playback finishes + + 0 + What to do after playback finishes - - - "normal" + + + 0 Mode used for seeking 10 Time amount to seek with single press of arrow keys - - "second" - Unit to use with seeking value + + 0 + Unit ID to use with seeking value true @@ -50,13 +46,13 @@ - + 0 Offset time for audio tracks relative to video (milliseconds) - + 0 Offset time for subtitle tracks relative to video (milliseconds) @@ -95,7 +91,7 @@ - '[{"apply":false,"name":"vah264dec","rank":300}]' + '{}' Custom values for GStreamer plugin ranking @@ -108,8 +104,8 @@ false Enable to use adaptive streaming for YouTube - - "hfr" + + 1 Max YouTube video quality type diff --git a/src/actions.js b/src/actions.js index 53201880..1ac67648 100644 --- a/src/actions.js +++ b/src/actions.js @@ -1,5 +1,6 @@ const { Gtk } = imports.gi; const Dialogs = imports.src.dialogs; +const Prefs = imports.src.prefs; const Misc = imports.src.misc; var actions = { @@ -42,7 +43,7 @@ function handleAction(action, window) new Dialogs.UriDialog(window); break; case 'prefs': - new Dialogs.PrefsDialog(window); + new Prefs.PrefsWindow(window); break; case 'shortcuts': if(!window.get_help_overlay()) { diff --git a/src/controls.js b/src/controls.js index afd539bd..5eea6ff0 100644 --- a/src/controls.js +++ b/src/controls.js @@ -469,7 +469,7 @@ class ClapperControls extends Gtk.Box scrollController.connect('scroll', clapperWidget._onScroll.bind(clapperWidget)); this.volumeButton.add_controller(scrollController); - const initialVolume = (settings.get_string('volume-initial') === 'custom') + const initialVolume = (settings.get_boolean('volume-custom')) ? settings.get_int('volume-value') / 100 : settings.get_double('volume-last'); diff --git a/src/dialogs.js b/src/dialogs.js index f7e16c9d..b5cefc63 100644 --- a/src/dialogs.js +++ b/src/dialogs.js @@ -3,8 +3,6 @@ const System = imports.system; const Debug = imports.src.debug; const FileOps = imports.src.fileOps; const Misc = imports.src.misc; -const Prefs = imports.src.prefs; -const PrefsBase = imports.src.prefsBase; const { debug } = Debug; @@ -247,88 +245,6 @@ class ClapperResumeDialog extends Gtk.MessageDialog } }); -var PrefsDialog = GObject.registerClass( -class ClapperPrefsDialog extends Gtk.Dialog -{ - _init(window) - { - super._init({ - transient_for: window, - destroy_with_parent: true, - modal: true, - title: 'Preferences', - default_width: 460, - default_height: 400, - }); - - const pages = [ - { - title: 'Player', - pages: [ - { - title: 'General', - widget: Prefs.GeneralPage, - }, - { - title: 'Behaviour', - widget: Prefs.BehaviourPage, - }, - { - title: 'Audio', - widget: Prefs.AudioPage, - }, - { - title: 'Subtitles', - widget: Prefs.SubtitlesPage, - }, - { - title: 'Network', - widget: Prefs.NetworkPage, - }, - { - title: 'YouTube', - widget: Prefs.YouTubePage, - } - ] - }, - { - title: 'Advanced', - pages: [ - { - title: 'GStreamer', - widget: Prefs.GStreamerPage, - }, - { - title: 'Tweaks', - widget: Prefs.TweaksPage, - } - ] - } - ]; - - const prefsNotebook = new PrefsBase.Notebook(pages); - prefsNotebook.add_css_class('prefsnotebook'); - - const area = this.get_content_area(); - area.append(prefsNotebook); - - this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this)); - this.show(); - } - - _onCloseRequest(dialog) - { - debug('closing prefs dialog'); - - dialog.disconnect(this.closeSignal); - this.closeSignal = null; - - const area = dialog.get_content_area(); - const notebook = area.get_first_child(); - notebook._onClose(); - } -}); - var AboutDialog = GObject.registerClass( class ClapperAboutDialog extends Gtk.AboutDialog { diff --git a/src/player.js b/src/player.js index d75b94d0..1d07d06d 100644 --- a/src/player.js +++ b/src/player.js @@ -44,7 +44,7 @@ class ClapperPlayer extends GstClapper.Clapper this.ytClient = null; this.playlistWidget = new PlaylistWidget(); - this.seek_done = true; + this.seekDone = true; this.needsFastSeekRestore = false; this.customVideoTitle = null; @@ -70,6 +70,7 @@ class ClapperPlayer extends GstClapper.Clapper set_and_bind_settings() { const settingsToSet = [ + 'after-playback', 'seeking-mode', 'audio-offset', 'subtitle-offset', @@ -81,7 +82,6 @@ class ClapperPlayer extends GstClapper.Clapper this._onSettingsKeyChanged(settings, key); const flag = Gio.SettingsBindFlags.GET; - settings.bind('keep-last-frame', this.widget, 'keep-last-frame', flag); settings.bind('subtitle-font', this.pipeline, 'subtitle-font-desc', flag); } @@ -96,25 +96,23 @@ class ClapperPlayer extends GstClapper.Clapper set_all_plugins_ranks() { - let data = []; + let data = {}; /* Set empty plugin list if someone messed it externally */ try { data = JSON.parse(settings.get_string('plugin-ranking')); - if(!Array.isArray(data)) - throw new Error('plugin ranking data is not an array!'); + if(Array.isArray(data)) { + data = {}; + throw new Error('plugin ranking data is not an object'); + } } catch(err) { debug(err); - settings.set_string('plugin-ranking', "[]"); + settings.set_string('plugin-ranking', "{}"); } - for(let plugin of data) { - if(!plugin.apply || !plugin.name) - continue; - - this.set_plugin_rank(plugin.name, plugin.rank); - } + for(let plugin of Object.keys(data)) + this.set_plugin_rank(plugin, data[plugin]); } set_plugin_rank(name, rank) @@ -284,7 +282,7 @@ class ClapperPlayer extends GstClapper.Clapper if(this.needsTocUpdate) return; - this.seek_done = false; + this.seekDone = false; if(this.state === GstClapper.ClapperState.STOPPED) this.pause(); @@ -292,8 +290,6 @@ class ClapperPlayer extends GstClapper.Clapper if(position < 0) position = 0; - debug(`${this.seekingMode} seeking to position: ${position}`); - super.seek(position); } @@ -304,13 +300,12 @@ class ClapperPlayer extends GstClapper.Clapper seek_chapter(seconds) { - if(this.seekingMode !== 'fast') { + if(this.seek_mode !== GstClapper.ClapperSeekMode.FAST) { this.seek_seconds(seconds); return; } this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); - this.seekingMode = 'normal'; this.needsFastSeekRestore = true; this.seek_seconds(seconds); @@ -318,20 +313,19 @@ class ClapperPlayer extends GstClapper.Clapper adjust_position(isIncrease) { - this.seek_done = false; + this.seekDone = false; const { controls } = this.widget.get_ancestor(Gtk.Grid); const max = controls.positionAdjustment.get_upper(); - const seekingUnit = settings.get_string('seeking-unit'); let seekingValue = settings.get_int('seeking-value'); - switch(seekingUnit) { - case 'minute': - seekingValue *= 60; + switch(settings.get_int('seeking-unit')) { + case 2: /* Percentage */ + seekingValue *= max / 100; break; - case 'percentage': - seekingValue = max * seekingValue / 100; + case 1: /* Minute */ + seekingValue *= 60; break; default: break; @@ -538,16 +532,15 @@ class ClapperPlayer extends GstClapper.Clapper const clapperWidget = player.widget.get_ancestor(Gtk.Grid); if(!clapperWidget) return; - if(!this.seek_done && state !== GstClapper.ClapperState.BUFFERING) { + if(!this.seekDone && state !== GstClapper.ClapperState.BUFFERING) { clapperWidget.updateTime(); if(this.needsFastSeekRestore) { this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); - this.seekingMode = 'fast'; this.needsFastSeekRestore = false; } - this.seek_done = true; + this.seekDone = true; debug('seeking finished'); clapperWidget._onPlayerPositionUpdated(this, this.position); @@ -566,7 +559,8 @@ class ClapperPlayer extends GstClapper.Clapper if(this.playlistWidget._handleStreamEnded(player)) return; - if(settings.get_boolean('close-auto')) { + /* After playback equal 2 means close the app */ + if(settings.get_int('after-playback') === 2) { /* Stop will be automatically called soon afterwards */ this.quitOnStop = true; this._performCloseCleanup(this.widget.get_root()); @@ -640,16 +634,18 @@ class ClapperPlayer extends GstClapper.Clapper let root, value, action; switch(key) { + case 'after-playback': + this.widget.keep_last_frame = (settings.get_int(key) === 1); + break; case 'seeking-mode': - this.seekingMode = settings.get_string('seeking-mode'); - switch(this.seekingMode) { - case 'fast': + switch(settings.get_int(key)) { + case 2: /* Fast */ this.set_seek_mode(GstClapper.ClapperSeekMode.FAST); break; - case 'accurate': + case 1: /* Accurate */ this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE); break; - default: + default: /* Normal */ this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT); break; } @@ -669,12 +665,12 @@ class ClapperPlayer extends GstClapper.Clapper root[action + '_css_class'](gpuClass); break; case 'audio-offset': - value = Math.round(settings.get_double(key) * -Gst.MSECOND); + value = Math.round(settings.get_int(key) * -Gst.MSECOND); this.set_audio_video_offset(value); debug(`set audio-video offset: ${value}`); break; case 'subtitle-offset': - value = Math.round(settings.get_double(key) * -Gst.MSECOND); + value = Math.round(settings.get_int(key) * -Gst.MSECOND); this.set_subtitle_video_offset(value); debug(`set subtitle-video offset: ${value}`); break; diff --git a/src/prefs.js b/src/prefs.js index 6d4047fb..d2a657d8 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -1,354 +1,475 @@ -const { GObject, Gst, Gtk, Pango } = imports.gi; +const { Adw, GObject, Gio, Gst, Gtk } = imports.gi; +const Debug = imports.src.debug; const Misc = imports.src.misc; -const PrefsBase = imports.src.prefsBase; +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, + 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, }; -var GeneralPage = GObject.registerClass( -class ClapperGeneralPage extends PrefsBase.Grid +const widgetOpts = { + halign: Gtk.Align.CENTER, + valign: Gtk.Align.CENTER, +}; + +function getCommonProps() { - _init() + 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.addTitle('Startup'); - this.addCheckButton('Auto enter fullscreen', 'fullscreen-auto'); + this._schemaName = null; + this._bindProp = null; - this.addTitle('Volume'); - const comboBox = this.addComboBoxText('Initial value', [ - ['restore', "Restore"], - ['custom', "Custom"], - ], 'volume-initial'); - const spinButton = this.addSpinButton('Value (percentage)', 0, 200, 'volume-value'); - this._onVolumeInitialChanged(spinButton, comboBox); - comboBox.connect('changed', this._onVolumeInitialChanged.bind(this, spinButton)); - - this.addTitle('Finish'); - this.addCheckButton('Keep showing last frame', 'keep-last-frame'); - this.addCheckButton('Close after playback', 'close-auto'); + this.add_suffix(widget); + this.set_activatable_widget(widget); } - _onVolumeInitialChanged(spinButton, comboBox) + set schema_name(value) { - const value = comboBox.get_active_id(); - spinButton.set_visible(value === 'custom'); - } -}); - -var BehaviourPage = GObject.registerClass( -class ClapperBehaviourPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Seeking'); - this.addComboBoxText('Mode', [ - ['normal', "Normal"], - ['accurate', "Accurate"], - ['fast', "Fast"], - ], 'seeking-mode'); - this.addComboBoxText('Unit', [ - ['second', "Second"], - ['minute', "Minute"], - ['percentage', "Percentage"], - ], 'seeking-unit'); - this.addSpinButton('Value', 1, 99, 'seeking-value'); - - this.addTitle('Resume'); - this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled'); - - this.addTitle('Floating Mode'); - this.addCheckButton('Show on all workspaces', 'floating-stick'); - } -}); - -var AudioPage = GObject.registerClass( -class ClapperAudioPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Synchronization'); - this.addSpinButton('Offset (milliseconds)', -1000, 1000, 'audio-offset', 25); - - this.addTitle('Processing'); - this.addPlayFlagCheckButton('Only use native audio formats', Gst.PlayFlags.NATIVE_AUDIO); - } -}); - -var SubtitlesPage = GObject.registerClass( -class ClapperSubtitlesPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - /* FIXME: This should be moved to subtitles popup and displayed only when - external subtitles were added for easier customization per video. */ - //this.addTitle('Synchronization'); - //this.addSpinButton('Offset (milliseconds)', -5000, 5000, 'subtitle-offset', 25); - - this.addTitle('External Subtitles'); - this.addFontButton('Default font', 'subtitle-font'); - } -}); - -var NetworkPage = GObject.registerClass( -class ClapperNetworkPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Client'); - this.addPlayFlagCheckButton('Progressive download buffering', Gst.PlayFlags.DOWNLOAD); - - this.addTitle('Server'); - const webServer = this.addCheckButton('Control player remotely', 'webserver-enabled'); - const serverPort = this.addSpinButton('Listening port', 1024, 65535, 'webserver-port'); - webServer.bind_property('active', serverPort, 'visible', GObject.BindingFlags.SYNC_CREATE); - const webApp = this.addCheckButton('Start built-in web application', 'webapp-enabled'); - webServer.bind_property('active', webApp, 'visible', GObject.BindingFlags.SYNC_CREATE); - const webAppPort = this.addSpinButton('Web application port', 1024, 65535, 'webapp-port'); - webServer.bind_property('active', webAppPort, 'visible', GObject.BindingFlags.SYNC_CREATE); - } -}); - -var YouTubePage = GObject.registerClass( -class ClapperYouTubePage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('YouTube'); - this.addCheckButton('Prefer adaptive streaming', 'yt-adaptive-enabled'); - this.addComboBoxText('Max quality', [ - ['normal', "Normal"], - ['hfr', "HFR"], - ], 'yt-quality-type'); - } -}); - -var GStreamerPage = GObject.registerClass( -class ClapperGStreamerPage extends PrefsBase.Grid -{ - _init() - { - super._init(); - - this.addTitle('Plugin Ranking'); - const listStore = new Gtk.ListStore(); - listStore.set_column_types([ - GObject.TYPE_BOOLEAN, - GObject.TYPE_STRING, - GObject.TYPE_STRING, - ]); - const treeView = new Gtk.TreeView({ - hexpand: true, - vexpand: true, - enable_search: false, - model: listStore, - }); - const treeSelection = treeView.get_selection(); - - const apply = new Gtk.TreeViewColumn({ - title: "Apply", - }); - const name = new Gtk.TreeViewColumn({ - title: "Plugin", - expand: true, - }); - const rank = new Gtk.TreeViewColumn({ - title: "Rank", - min_width: 90, - }); - - const applyCell = new Gtk.CellRendererToggle(); - const nameCell = new Gtk.CellRendererText({ - editable: true, - placeholder_text: "Insert plugin name", - }); - const rankCell = new Gtk.CellRendererText({ - editable: true, - weight: Pango.Weight.BOLD, - placeholder_text: "Insert plugin rank", - }); - - apply.pack_start(applyCell, true); - name.pack_start(nameCell, true); - rank.pack_start(rankCell, true); - - apply.add_attribute(applyCell, 'active', 0); - name.add_attribute(nameCell, 'text', 1); - rank.add_attribute(rankCell, 'text', 2); - - treeView.insert_column(apply, 0); - treeView.insert_column(name, 1); - treeView.insert_column(rank, 2); - - const frame = new Gtk.Frame({ - child: treeView - }); - this.addToGrid(frame); - - const addButton = new Gtk.Button({ - icon_name: 'list-add-symbolic', - halign: Gtk.Align.END, - }); - const removeButton = new Gtk.Button({ - icon_name: 'list-remove-symbolic', - sensitive: false, - halign: Gtk.Align.END, - }); - const label = new Gtk.Label({ - label: 'Changes require player restart', - halign: Gtk.Align.START, - hexpand: true, - ellipsize: Pango.EllipsizeMode.END, - }); - const box = new Gtk.Box({ - orientation: Gtk.Orientation.HORIZONTAL, - spacing: 6, - hexpand: true, - }); - box.append(label); - box.append(removeButton); - box.append(addButton); - this.addToGrid(box); - - applyCell.connect('toggled', this._onApplyCellEdited.bind(this)); - nameCell.connect('edited', this._onNameCellEdited.bind(this)); - rankCell.connect('edited', this._onRankCellEdited.bind(this)); - addButton.connect('clicked', this._onAddButtonClicked.bind(this, listStore)); - removeButton.connect('clicked', this._onRemoveButtonClicked.bind(this, listStore)); - treeSelection.connect('changed', this._onTreeSelectionChanged.bind(this, removeButton)); - - this.settingsChangedSignal = settings.connect( - 'changed::plugin-ranking', this.refreshListStore.bind(this, listStore) - ); - - this.refreshListStore(listStore); + this._schemaName = value; } - refreshListStore(listStore) + vfunc_realize() { - const data = JSON.parse(settings.get_string('plugin-ranking')); - listStore.clear(); + super.vfunc_realize(); - for(let plugin of data) { - listStore.set( - listStore.append(), - [0, 1, 2], [ - plugin.apply || false, - plugin.name || '', - plugin.rank || 0 - ] + if(this._schemaName && this._bindProp) { + settings.bind(this._schemaName, + this.activatable_widget, this._bindProp, flags ); } - } - - updatePlugin(index, prop, value) - { - const data = JSON.parse(settings.get_string('plugin-ranking')); - data[index][prop] = value; - settings.set_string('plugin-ranking', JSON.stringify(data)); - } - - _onTreeSelectionChanged(removeButton, treeSelection) - { - const [isSelected, model, iter] = treeSelection.get_selected(); - this.activeIndex = -1; - - if(isSelected) { - this.activeIndex = Number(model.get_string_from_iter(iter)); - } - - removeButton.set_sensitive(this.activeIndex >= 0); - } - - _onAddButtonClicked(listStore, button) - { - const data = JSON.parse(settings.get_string('plugin-ranking')); - data.push({ - apply: false, - name: '', - rank: 0, - }); - settings.set_string('plugin-ranking', JSON.stringify(data)); - } - - _onRemoveButtonClicked(listStore, button) - { - if(this.activeIndex < 0) - return; - - const data = JSON.parse(settings.get_string('plugin-ranking')); - data.splice(this.activeIndex, 1); - settings.set_string('plugin-ranking', JSON.stringify(data)); - } - - _onApplyCellEdited(cell, path) - { - const newState = !cell.active; - this.updatePlugin(path, 'apply', newState); - } - - _onNameCellEdited(cell, path, newText) - { - newText = newText.trim(); - this.updatePlugin(path, 'name', newText); - } - - _onRankCellEdited(cell, path, newText) - { - newText = newText.trim(); - - if(isNaN(newText)) - newText = 0; - - this.updatePlugin(path, 'rank', Number(newText)); - } - - _onClose() - { - super._onClose('gstreamer'); - - settings.disconnect(this.settingsChangedSignal); - this.settingsChangedSignal = null; + this._schemaName = null; } }); -var TweaksPage = GObject.registerClass( -class ClapperTweaksPage extends PrefsBase.Grid +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', +}, +class ClapperPrefsSwitch extends PrefsActionRow +{ + _init() + { + super._init(new Gtk.Switch(widgetOpts)); + this._bindProp = 'active'; + } +}); + +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( +class PrefsPluginFeature 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); + + if(enableSwitch.active) + settingsData[this.title] = this.currentRank; + else if(settingsData[this.title]) + delete settingsData[this.title]; + + 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( +class ClapperPrefsPluginExpander extends Adw.ExpanderRow +{ + _init(plugin) + { + super._init({ + title: plugin, + show_enable_switch: false, + }); + + this.expandSignal = this.connect( + 'notify::expanded', this._onExpandedNotify.bind(this) + ); + } + + _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: `file://${Misc.getClapperPath()}/ui/preferences-plugin-ranking-subpage.ui`, + InternalChildren: ['decoders_group'], +}, +class ClapperPrefsPluginRankingSubpage extends Gtk.Box { _init() { super._init(); - this.addTitle('Appearance'); - this.addCheckButton('Enable dark theme', 'dark-theme'); + if(!Gst.is_initialized()) + Gst.init(null); - this.addTitle('Performance'); - this.addCheckButton('Render window shadows', 'render-shadows'); + const gstRegistry = Gst.Registry.get(); + const decoders = gstRegistry.feature_filter(this._decodersFilterCb, false); + + const plugins = {}; + 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(); + + plugins[pluginName].push({ + name: decName, + rank: decoder.get_rank(), + enabled: this.settingsData[decName] != null, + }); + } + + 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) + this._decoders_group.add(new PrefsPluginExpander(plugin)); + } + + _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: `file://${Misc.getClapperPath()}/ui/preferences-window.ui`, +}, +class ClapperPrefsWindow extends Adw.PreferencesWindow +{ + _init(window) + { + super._init({ + transient_for: window, + }); + + this.show(); } }); diff --git a/src/prefsBase.js b/src/prefsBase.js deleted file mode 100644 index 2d12442a..00000000 --- a/src/prefsBase.js +++ /dev/null @@ -1,238 +0,0 @@ -const { Gio, GObject, Gtk } = imports.gi; -const Debug = imports.src.debug; -const Misc = imports.src.misc; - -const { debug } = Debug; -const { settings } = Misc; - -var Notebook = GObject.registerClass( -class ClapperPrefsNotebook extends Gtk.Notebook -{ - _init(pages, isSubpage) - { - super._init({ - show_border: false, - vexpand: true, - hexpand: true, - }); - - if(isSubpage) { - this.set_tab_pos(Gtk.PositionType.LEFT); - this.add_css_class('prefssubpage'); - } - - this.addArrayPages(pages); - } - - addArrayPages(array) - { - for(let obj of array) - this.addObjectPages(obj); - } - - addObjectPages(item) - { - const widget = (item.pages) - ? new Notebook(item.pages, true) - : new item.widget(); - - this.addToNotebook(widget, item.title); - } - - addToNotebook(widget, title) - { - const label = new Gtk.Label({ - label: title, - }); - this.append_page(widget, label); - } - - _onClose() - { - const totalPages = this.get_n_pages(); - let index = 0; - - while(index < totalPages) { - const page = this.get_nth_page(index); - page._onClose(); - index++; - } - } -}); - -var Grid = GObject.registerClass( -class ClapperPrefsGrid extends Gtk.Grid -{ - _init() - { - super._init({ - row_spacing: 6, - column_spacing: 20, - }); - - this.flag = Gio.SettingsBindFlags.DEFAULT; - this.gridIndex = 0; - this.widgetDefaults = { - width_request: 160, - halign: Gtk.Align.END, - valign: Gtk.Align.CENTER, - }; - } - - addToGrid(leftWidget, rightWidget) - { - let spanWidth = 2; - - if(rightWidget) { - spanWidth = 1; - rightWidget.bind_property('visible', leftWidget, 'visible', - GObject.BindingFlags.SYNC_CREATE - ); - this.attach(rightWidget, 1, this.gridIndex, 1, 1); - } - - this.attach(leftWidget, 0, this.gridIndex, spanWidth, 1); - this.gridIndex++; - - return rightWidget || leftWidget; - } - - addTitle(text) - { - const label = this.getLabel(text, true); - - return this.addToGrid(label); - } - - addComboBoxText(text, entries, setting) - { - const label = this.getLabel(text + ':'); - const widget = this.getComboBoxText(entries, setting); - - return this.addToGrid(label, widget); - } - - addSpinButton(text, min, max, setting, precision) - { - const label = this.getLabel(text + ':'); - const widget = this.getSpinButton(min, max, setting, precision); - - return this.addToGrid(label, widget); - } - - addCheckButton(text, setting) - { - const widget = this.getCheckButton(text, setting); - - return this.addToGrid(widget); - } - - addPlayFlagCheckButton(text, flag) - { - const checkButton = this.addCheckButton(text); - const playFlags = settings.get_int('play-flags'); - - checkButton.active = ((playFlags & flag) === flag); - checkButton.connect('toggled', this._onPlayFlagToggled.bind(this, flag)); - - return checkButton; - } - - addFontButton(text, setting) - { - const label = this.getLabel(text + ':'); - const widget = this.getFontButton(setting); - - return this.addToGrid(label, widget); - } - - getLabel(text, isTitle) - { - const marginTop = (isTitle && this.gridIndex > 0) ? 16 : 0; - const marginBottom = (isTitle) ? 2 : 0; - - let marginLR = 0; - - if(isTitle) - text = '' + text + ''; - else - marginLR = 12; - - return new Gtk.Label({ - label: text, - use_markup: true, - hexpand: true, - halign: Gtk.Align.START, - margin_top: marginTop, - margin_bottom: marginBottom, - margin_start: marginLR, - margin_end: marginLR, - }); - } - - getComboBoxText(entries, setting) - { - const comboBox = new Gtk.ComboBoxText(this.widgetDefaults); - - for(let entry of entries) - comboBox.append(entry[0], entry[1]); - - settings.bind(setting, comboBox, 'active-id', this.flag); - - return comboBox; - } - - getSpinButton(min, max, setting, precision) - { - precision = precision || 1; - - const spinButton = new Gtk.SpinButton(this.widgetDefaults); - spinButton.set_range(min, max); - spinButton.set_digits(precision % 1 === 0 ? 0 : 3); - spinButton.set_increments(precision, 1); - settings.bind(setting, spinButton, 'value', this.flag); - - return spinButton; - } - - getCheckButton(text, setting) - { - const checkButton = new Gtk.CheckButton({ - label: text || null, - }); - - if(setting) - settings.bind(setting, checkButton, 'active', this.flag); - - return checkButton; - } - - getFontButton(setting) - { - const fontButton = new Gtk.FontButton({ - use_font: true, - use_size: true, - }); - settings.bind(setting, fontButton, 'font', this.flag); - - return fontButton; - } - - _onPlayFlagToggled(flag, button) - { - let playFlags = settings.get_int('play-flags'); - - if(button.active) - playFlags |= flag; - else - playFlags &= ~flag; - - settings.set_int('play-flags', playFlags); - } - - _onClose(name) - { - if(name) - debug(`cleanup of prefs ${name} page`); - } -}); diff --git a/src/widget.js b/src/widget.js index 4c6fcc1c..af3a1791 100644 --- a/src/widget.js +++ b/src/widget.js @@ -492,7 +492,7 @@ class ClapperWidget extends Gtk.Grid if( !this.isSeekable || this.controls.isPositionDragging - || !player.seek_done + || !player.seekDone ) return; diff --git a/src/youtube.js b/src/youtube.js index b4e0fdbc..7cd7d8e1 100644 --- a/src/youtube.js +++ b/src/youtube.js @@ -317,7 +317,7 @@ var YouTubeClient = GObject.registerClass({ width: monitor.geometry.width * monitor.scale_factor, height: monitor.geometry.height * monitor.scale_factor, codec: 'h264', - type: settings.get_string('yt-quality-type'), + type: YTItags.QualityType[settings.get_int('yt-quality-type')], adaptive: settings.get_boolean('yt-adaptive-enabled'), }; diff --git a/src/youtubeItags.js b/src/youtubeItags.js index 374ea8a1..09b69326 100644 --- a/src/youtubeItags.js +++ b/src/youtubeItags.js @@ -1,3 +1,8 @@ +var QualityType = { + 0: 'normal', + 1: 'hfr', +}; + const Itags = { video: { h264: { diff --git a/ui/preferences-plugin-ranking-subpage.ui b/ui/preferences-plugin-ranking-subpage.ui new file mode 100644 index 00000000..6d98919e --- /dev/null +++ b/ui/preferences-plugin-ranking-subpage.ui @@ -0,0 +1,30 @@ + + + + diff --git a/ui/preferences-window.ui b/ui/preferences-window.ui new file mode 100644 index 00000000..8a71aa4b --- /dev/null +++ b/ui/preferences-window.ui @@ -0,0 +1,292 @@ + + + + + 0 + 99 + 1 + 1 + + + 0 + 150 + 1 + 1 + + + -1000 + 1000 + 25 + 1 + + + 1024 + 65535 + 1 + 1 + + + 1024 + 65535 + 1 + 1 + +