diff --git a/README.md b/README.md index e7bd7f0..a689aff 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ For Desktop Widgets I'm Using [Circular Widgets](https://extensions.gnome.org/ex - Change Audio source from Menu (To change right/left click on Visualizer) - Change Visualizer size - Increase or Decrease Bands +- Choose how many bands will appear on display +- Now you can Flip Visualizer +- Added older version Gnome Shell v3.36 to v43.0 More Feature will be added in Future diff --git a/src/extension.js b/src/extension.js index 4af4d54..7d91069 100644 --- a/src/extension.js +++ b/src/extension.js @@ -18,7 +18,7 @@ function enable() { function disable() { if (timeoutId) { GLib.Source.remove(timeoutId); - tiemoutId = null; + timeoutId = null; } visual.onDestroy(); visual = null; diff --git a/src/metadata.json b/src/metadata.json index d544bad..7991f79 100644 --- a/src/metadata.json +++ b/src/metadata.json @@ -1,11 +1,17 @@ - { - "_generated": "Generated by SweetTooth, do not edit", - "description": "A Sound Visualizer Based On Gstreamer", + "_generated": "Generated by SweetTooth, do not edit", + "description": "A Real Time Sound Visualizer Based On Gstreamer", "name": "Sound Visualizer", "settings-schema": "org.gnome.shell.extensions.visualizer", - "shell-version": [ "43" ], + "shell-version": [ + "3.36", + "3.38", + "40", + "41", + "42", + "43" + ], "url": "https://github.com/raihan2000/visualizer", - "uuid": "visualizer@sound.org", - "version": 1 + "uuid": "visualizer@sound.org", + "version": 4 } diff --git a/src/prefs.js b/src/prefs.js index 8108285..c9e5226 100644 --- a/src/prefs.js +++ b/src/prefs.js @@ -1,17 +1,63 @@ 'use strict'; -const { Adw, Gio, Gtk, Gdk, GLib } = imports.gi; +const { Gio, Gtk, Gdk, GLib, GObject } = imports.gi; const Params = imports.misc.params; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); +const Config = imports.misc.config; +const [major, minor] = Config.PACKAGE_VERSION.split('.').map(s => Number(s)); +let Adw; -function init() {} +function init() { +} function fillPreferencesWindow(window) { + Adw = imports.gi.Adw; let prefs = new PrefsWindow(window); prefs.fillPrefsWindow(); } +function buildPrefsWidget() { + let widget = new prefsWidget(); + (major < 40) ? widget.show_all(): widget.show(); + return widget; +} + +const prefsWidget = GObject.registerClass( + class prefsWidget extends Gtk.Notebook { + + _init(params) { + super._init(params); + this._settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.visualizer'); + this.margin = 20; + + let grid = new Gtk.Grid(); + attachItems(grid, new Gtk.Label({ label: 'Flip the Visualizer' }), getSwitch('flip-visualizer', this._settings), 0); + attachItems(grid, new Gtk.Label({ label: 'Visualizer Height' }), getSpinButton(false, 'visualizer-height', 1, 200, 1, this._settings), 1); + attachItems(grid, new Gtk.Label({ label: 'Visualizer Width' }), getSpinButton(false, 'visualizer-width', 1, 1920, 1, this._settings), 2); + attachItems(grid, new Gtk.Label({ label: 'Spects Line Width' }), getSpinButton(false, 'spects-line-width', 1, 20, 1, this._settings), 3); + attachItems(grid, new Gtk.Label({ label: 'Change Spects Band to Get' }), getSpinButton(false, 'total-spects-band', 1, 256, 1, this._settings), 4); + this.attachHybridRow(grid, new Gtk.Label({ label: 'Override Spect Value' }), new Gtk.Label({ label: 'Set Spects Value' }), getSwitch('spect-over-ride-bool', this._settings), getSpinButton(false, 'spect-over-ride', 1, 256, 1, this._settings), 5); + this.append_page(grid, new Gtk.Label({ label: 'Visualizer' })); + let aboutBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); + if (major < 40) { + aboutBox.add(new Gtk.Label({ label: Me.metadata.name })); + aboutBox.add(new Gtk.Label({ label: 'Version: ' + Me.metadata.version.toString() })); + } else { + aboutBox.append(new Gtk.Label({ label: Me.metadata.name })); + aboutBox.append(new Gtk.Label({ label: 'Version: ' + Me.metadata.version.toString() })); + } + this.append_page(aboutBox, new Gtk.Label({ label: 'About' })); + } + + attachHybridRow(grid, label, label1, button, button1, row) { + grid.attach(label, 0, row, 1, 1); + grid.attach(button, 1, row, 1, 1); + grid.attach(label1, 0, row + 1, 1, 1); + grid.attach(button1, 1, row + 1, 1, 1); + } + }); + class PrefsWindow { constructor(window) { this._window = window; @@ -42,11 +88,10 @@ class PrefsWindow { if (title !== undefined) { group = new Adw.PreferencesGroup({ title: title, - /*margin_top: 5, - margin_bottom: 5,*/ + //margin_top: 5, + //margin_bottom: 5, }); - } - else { + } else { group = new Adw.PreferencesGroup(); } page.add(group); @@ -62,35 +107,25 @@ class PrefsWindow { row.activatable_widget = widget; } - // create a new Adw.ActionRow to insert an option into a prefsGroup - append_switch(group, title, key) { - let button = new Gtk.Switch({ - active: key, - valign: Gtk.Align.CENTER, + append_expander_row(group, titleEx, title, key, key1) { + let expand_row = new Adw.ExpanderRow({ + title: titleEx, + show_enable_switch: true, + expanded: this._settings.get_boolean(key), + enable_expansion: this._settings.get_boolean(key) }); - - this._settings.bind( - key, - button, - 'active', - Gio.SettingsBindFlags.DEFAULT - ); - this.append_row(group, title, button); - } - - append_spin_button(group, title, is_double, key, min, max, step) { - let v = 0; - if (is_double) { - v = this._settings.get_double(key); - } - else { - v = this._settings.get_int(key); - } - let spin = Gtk.SpinButton.new_with_range(min, max, step); - spin.set_value(v); - this._settings.bind(key, spin, 'value', Gio.SettingsBindFlags.DEFAULT); - this.append_row(group, title, spin); - } + let row = new Adw.ActionRow({ + title: title, + }); + expand_row.connect("notify::enable-expansion", (widget) => { + let settingArray = this._settings.get_boolean(key); + settingArray = widget.enable_expansion; + this._settings.set_value(key, new GLib.Variant('b', settingArray)); + }); + row.add_suffix(key1); + expand_row.add_row(row); + group.add(expand_row); + }; append_info_group(group, name, title) { let adw_group = new Adw.PreferencesGroup(); @@ -117,16 +152,39 @@ class PrefsWindow { fillPrefsWindow() { let visualWidget = this.create_page('Visualizer'); { let groupVisual = this.create_group(visualWidget); - this.append_spin_button(groupVisual, 'Visualizer Height', false, 'visualizer-height', 1, 200, 1); - this.append_spin_button(groupVisual, 'Visualizer Width', false, 'visualizer-width', 1, 1920, 1); - this.append_spin_button(groupVisual, 'Spects Line Width', false, 'spects-line-width', 1, 20, 1); - this.append_spin_button(groupVisual, 'Change Spects Value', false, 'total-spects-band', 1, 256, 1); + this.append_row(groupVisual, 'Flip the Visualizer', getSwitch('flip-visualizer', this._settings)); + this.append_row(groupVisual, 'Visualizer Height', getSpinButton(false, 'visualizer-height', 1, 200, 1, this._settings)); + this.append_row(groupVisual, 'Visualizer Width', getSpinButton(false, 'visualizer-width', 1, 1920, 1, this._settings)); + this.append_row(groupVisual, 'Spects Line Width', getSpinButton(false, 'spects-line-width', 1, 20, 1, this._settings)); + this.append_row(groupVisual, 'Change Spects Band to Get', getSpinButton(false, 'total-spects-band', 1, 256, 1, this._settings)); + this.append_expander_row(groupVisual, 'Override Spect Value', 'Set Spects Value', 'spect-over-ride-bool', getSpinButton(false, 'spect-over-ride', 1, 256, 1, this._settings)); } let aboutPage = this.create_page('About'); { let groupAbout = this.create_group(aboutPage); - this.append_info_group(groupAbout, Me.metadata.name, - Me.metadata.version.toString()); + this.append_info_group(groupAbout, Me.metadata.name, Me.metadata.version.toString()); } } } + +function attachItems(grid, label, widget, row) { + grid.set_column_spacing(200); + grid.set_row_spacing(25); + grid.attach(label, 0, row, 1, 1); + grid.attach(widget, 1, row, 1, 1); +} + +function getSwitch(key, settings) { + let button = new Gtk.Switch({ active: key, valign: Gtk.Align.CENTER }); + settings.bind(key, button, 'active', Gio.SettingsBindFlags.DEFAULT); + return button +} + +function getSpinButton(is_double, key, min, max, step, settings) { + let v = 0; + (is_double) ? v = settings.get_double(key) : v = settings.get_int(key); + let spin = Gtk.SpinButton.new_with_range(min, max, step); + spin.set_value(v); + settings.bind(key, spin, 'value', Gio.SettingsBindFlags.DEFAULT); + return spin; +} diff --git a/src/schemas/org.gnome.shell.extensions.visualizer.gschema.xml b/src/schemas/org.gnome.shell.extensions.visualizer.gschema.xml index 9647756..c20986b 100644 --- a/src/schemas/org.gnome.shell.extensions.visualizer.gschema.xml +++ b/src/schemas/org.gnome.shell.extensions.visualizer.gschema.xml @@ -1,33 +1,42 @@ - - + + - (400, 100) - Location of visualizer - Location of visualizer + (400, 100) + Location of visualizer + Location of visualizer - - 150 - Vertical Size of DrawingArea - - + 150 + Vertical Size of DrawingArea + - 720 - Horizontal Size of DrawingArea - - + 720 + Horizontal Size of DrawingArea + - 5 - All Spect Bands width - - + 5 + All Spect Bands width + - 64 - Count Total Spects Bands - - + 64 + Count Total Spects Bands + + + false + Flip Visualizer + + + false + Override Spects Bands Boolean + Override Spects Bands Boolean + + + 64 + Override Spects Bands + Override Spects Bands + diff --git a/src/visual.js b/src/visual.js index f18419e..cd335d8 100644 --- a/src/visual.js +++ b/src/visual.js @@ -1,10 +1,11 @@ -const { Clutter, GObject, GLib, Gio, St, Gdk, Gst, Meta, Shell } = imports.gi; +const { Clutter, GObject, GLib, Gio, St, Gdk, Gst, Gvc, Meta, Shell } = imports.gi; const DND = imports.ui.dnd; const Cairo = imports.cairo; const ExtensionUtils = imports.misc.extensionUtils; const Main = imports.ui.main; const PopupMenu = imports.ui.popupMenu; -const Decoder = new TextDecoder(); +const Config = imports.misc.config; +const [major, minor] = Config.PACKAGE_VERSION.split('.').map(s => Number(s)); var Visualizer = GObject.registerClass( class musicVisualizer extends St.BoxLayout { @@ -15,7 +16,6 @@ var Visualizer = GObject.registerClass( can_focus: true }); this._visualMenuManager = new PopupMenu.PopupMenuManager(this); - this._menuItems = this.getMenuItem(); this._freq = []; this._actor = new St.DrawingArea(); this.add_child(this._actor); @@ -32,6 +32,8 @@ var Visualizer = GObject.registerClass( this.actorInit(); this._actor.connect('repaint', (area) => this.drawStuff(area)); this.setupGst(); + this.setDefaultSrc(); + this.getMenuItems(); this._update(); this.setPosition(); Main.layoutManager._backgroundGroup.add_child(this); @@ -41,7 +43,6 @@ var Visualizer = GObject.registerClass( Gst.init(null); this._pipeline = Gst.Pipeline.new("bin"); this._src = Gst.ElementFactory.make("pulsesrc", "src"); - this._src.set_property("device", this._menuItems[0]); this._spectrum = Gst.ElementFactory.make("spectrum", "spectrum"); this._spectrum.set_property("bands", this._spectBands); this._spectrum.set_property("threshold", -80); @@ -64,8 +65,7 @@ var Visualizer = GObject.registerClass( let [magbool, magnitudes] = struct.get_list("magnitude"); if (!magbool) { print('No magnitudes'); - } - else { + } else { for (let i = 0; i < this._spectBands; ++i) { this._freq[i] = magnitudes.get_nth(i) * -1; } @@ -81,14 +81,23 @@ var Visualizer = GObject.registerClass( } drawStuff(area) { + let values = this.getSpectBands(); let [width, height] = area.get_surface_size(); let cr = area.get_context(); let lineW = this._settings.get_int('spects-line-width'); - for (let i = 0; i < this._freq.length; i++) { + let flip = this._settings.get_boolean('flip-visualizer'); + for (let i = 0; i < values; i++) { cr.setSourceRGBA(1, this._freq[i] / 80, 1, 1); cr.setLineWidth(lineW); - cr.moveTo(lineW / 2 + i * width / this._spectBands, height); - cr.lineTo(lineW / 2 + i * width / this._spectBands, height * this._freq[i] / 80); + if (!flip) { + cr.moveTo(lineW / 2 + i * width / values, height); + cr.lineTo(lineW / 2 + i * width / values, height - 1); + cr.lineTo(lineW / 2 + i * width / values, height * this._freq[i] / 80); + } else { + cr.moveTo(lineW / 2 + i * width / values, 0); + cr.lineTo(lineW / 2 + i * width / values, 1); + cr.lineTo(lineW / 2 + i * width / values, height / 80 * (80 - this._freq[i])); + } cr.stroke(); } cr.$dispose(); @@ -98,6 +107,12 @@ var Visualizer = GObject.registerClass( this._actor.queue_repaint(); } + getSpectBands() { + let override = this._settings.get_boolean('spect-over-ride-bool'); + let values = this._settings.get_int('spect-over-ride'); + return (!override) ? this._spectBands : (values <= this._spectBands) ? values : this._spectBands + } + _getMetaRectForCoords(x, y) { this.get_allocation_box(); let rect = new Meta.Rectangle(); @@ -188,18 +203,60 @@ var Visualizer = GObject.registerClass( return this; } - getMenuItem() { + async getMenuItems() { try { - let [ok, out, err, exit] = GLib.spawn_command_line_sync(`sh -c "pactl list | grep -A2 'Source #' | grep 'Name: ' | cut -d' ' -f2"`); - if (out.length > 0) { - return Decoder.decode(out).trim().split('\n'); + this._menuItems = []; + let stream = await this.getStreams(); + for (let i = 0; i < stream.length; i++) { + if (stream[i] instanceof Gvc.MixerSink) { + this._menuItems.push(stream[i].get_name() + '.monitor'); + } else if (stream[i] instanceof Gvc.MixerSource) { + this._menuItems.push(stream[i].get_name()); + } + } + if (this._menuItems.length > 0) { + this._removeSource(this._streamId); } + } catch (e) { + logError(e); } - catch (e) { + } + + async setDefaultSrc() { + try { + this._defaultSrc = await this.getDefaultSrc(); + this._src.set_property('device', this._defaultSrc); + if (this._defaultSrc !== undefined) { + this._removeSource(this._defaultSrcId); + } + } catch (e) { logError(e); } } + getDefaultSrc() { + return new Promise((resolve, reject) => { + this._defaultSrcId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { + let stream = (major < 43) ? Main.panel.statusArea.aggregateMenu._volume._volumeMenu._output.stream : Main.panel.statusArea.quickSettings._volume._output.stream; + (stream !== null) ? resolve(stream.get_name() + '.monitor'): reject(Error('failure')); + return GLib.SOURCE_REMOVE; + }); + }); + } + + getStreams() { + return new Promise((resolve, reject) => { + this._streamId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { + let control = (major < 43) ? Main.panel.statusArea.aggregateMenu._volume._control : Main.panel.statusArea.quickSettings._volume._control; + if (control.get_state() == Gvc.MixerControlState.READY) { + let streams = control.get_streams(); + (streams.length > 0) ? resolve(streams): reject(Error('failure')) + } + return GLib.SOURCE_REMOVE; + }); + }); + } + vfunc_button_press_event() { let event = Clutter.get_current_event(); if (event.get_button() === 1) @@ -252,7 +309,7 @@ var Visualizer = GObject.registerClass( this._subMenuItem.push(item); } for (let k = 0; k < this._menuItems.length; k++) { - this._subMenuItem[k].setOrnament(this._menuItems[0] == this._menuItems[k] ? PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE); + this._subMenuItem[k].setOrnament(this._defaultSrc == this._menuItems[k] ? PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE); } this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this._menu.addAction("Visualizer Settings", () => { @@ -266,10 +323,9 @@ var Visualizer = GObject.registerClass( } onDestroy() { - if (this._menuTimeoutId) { - GLib.Source.remove(this._menuTimeoutId); - this._menuTimeoutId = null; - } + this._removeSource(this._menuTimeoutId); + this._removeSource(this._streamId); + this._removeSource(this._defaultSrcId); this._pipeline.set_state(Gst.State.NULL); Main.layoutManager._backgroundGroup.remove_child(this); } @@ -289,6 +345,15 @@ var Visualizer = GObject.registerClass( this.actorInit(); this._update(); }); + this._settings.connect('changed::spect-over-ride', () => this.getSpectBands()); + this._settings.connect('changed::spect-over-ride-bool', () => this.getSpectBands()); this._settings.connect('changed::spects-line-width', () => this._update()); } + + _removeSource(src) { + if (src) { + GLib.Source.remove(src); + src = null; + } + } });