From df443156d555b209ef69792562f06c5d9828b9eb Mon Sep 17 00:00:00 2001 From: "Sakith B." Date: Fri, 5 Jan 2024 16:44:50 +0530 Subject: [PATCH] Fixed bugs in menu slider Fixed bugs in button label relating to the artist The player proxy now polls the player till there is a length after it is initialized --- .../mediacontrols@cliffniff.github.com.pot | 2 +- src/extension.ts | 48 +++---- src/helpers/MenuSlider.ts | 124 +++++++++--------- src/helpers/PanelButton.ts | 108 ++++++++++----- src/helpers/PlayerProxy.ts | 51 ++++++- src/helpers/ScrollingLabel.ts | 2 +- src/types/dbus.ts | 2 +- 7 files changed, 212 insertions(+), 125 deletions(-) diff --git a/assets/locale/mediacontrols@cliffniff.github.com.pot b/assets/locale/mediacontrols@cliffniff.github.com.pot index f4e328f..1a6beb7 100644 --- a/assets/locale/mediacontrols@cliffniff.github.com.pot +++ b/assets/locale/mediacontrols@cliffniff.github.com.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-04 20:18+0530\n" +"POT-Creation-Date: 2024-01-05 16:39+0530\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/src/extension.ts b/src/extension.ts index ecbe78a..8399bd9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -92,6 +92,25 @@ export default class MediaControls extends Extension { debugLog("Enabled"); } + public disable() { + this.playerProxies = null; + + this.destroySettings(); + + this.watchIfaceInfo = null; + this.mprisIfaceInfo = null; + this.mprisPlayerIfaceInfo = null; + this.propertiesIfaceInfo = null; + this.watchProxy = null; + + this.removePanelButton(); + this.updateMediaNotificationVisiblity(true); + + Main.wm.removeKeybinding("mediacontrols-show-popup-menu"); + + debugLog("Disabled"); + } + public getPlayers() { const players: PlayerProxy[] = []; @@ -381,13 +400,15 @@ export default class MediaControls extends Extension { const isPlayerBlacklisted = this.isPlayerBlacklisted(playerProxy.identity, playerProxy.desktopEntry); if (isPlayerBlacklisted) { - debugLog("Player is blacklisted:", busName); return; } - playerProxy.onChanged("IsInvalid", this.setActivePlayer.bind(this)); playerProxy.onChanged("IsPinned", this.setActivePlayer.bind(this)); playerProxy.onChanged("PlaybackStatus", this.setActivePlayer.bind(this)); + playerProxy.onChanged("IsInvalid", () => { + this.setActivePlayer(); + this.panelBtn?.updateWidgets(WidgetFlags.MENU_PLAYERS); + }); this.playerProxies.set(busName, playerProxy); this.panelBtn?.updateWidgets(WidgetFlags.MENU_PLAYERS); @@ -402,8 +423,6 @@ export default class MediaControls extends Extension { } private setActivePlayer() { - debugLog("Setting active player"); - if (this.playerProxies.size === 0) { if (this.panelBtn != null) { this.removePanelButton(); @@ -424,9 +443,9 @@ export default class MediaControls extends Extension { break; } - if (this.panelBtn == null && chosenPlayer == null) { + if (chosenPlayer == null) { chosenPlayer = playerProxy; - } else if (this.panelBtn?.isSamePlayer(playerProxy) && chosenPlayer == null) { + } else if (this.panelBtn?.isSamePlayer(playerProxy)) { chosenPlayer = playerProxy; } else if (playerProxy.playbackStatus === PlaybackStatus.PLAYING) { chosenPlayer = playerProxy; @@ -522,21 +541,4 @@ export default class MediaControls extends Extension { this.cacheArt = null; this.blacklistedPlayers = null; } - - public disable() { - this.watchProxy = null; - this.watchIfaceInfo = null; - this.mprisIfaceInfo = null; - this.mprisPlayerIfaceInfo = null; - this.propertiesIfaceInfo = null; - this.playerProxies = null; - - this.removePanelButton(); - this.updateMediaNotificationVisiblity(true); - this.destroySettings(); - - Main.wm.removeKeybinding("mediacontrols-show-popup-menu"); - - debugLog("Disabled"); - } } diff --git a/src/helpers/MenuSlider.ts b/src/helpers/MenuSlider.ts index 65fd045..1a60b6a 100644 --- a/src/helpers/MenuSlider.ts +++ b/src/helpers/MenuSlider.ts @@ -14,60 +14,38 @@ class MenuSlider extends St.BoxLayout { private durationLabel: St.Label; private dragPaused: boolean; + private disabled: boolean; - constructor(initLocation: number, initLength: number, initPaused: boolean) { + constructor() { super({ vertical: true }); - initLocation = initLocation / 1000; - initLength = initLength / 1000; - - const initialValue = initLocation / initLength; - const elapsedText = msToHHMMSS(initLocation); - const durationText = msToHHMMSS(initLength); - - this.slider = new Slider.Slider(initialValue); + this.slider = new Slider.Slider(0); this.textBox = new St.BoxLayout(); this.elapsedLabel = new St.Label({ - text: elapsedText, + text: "00:00", xExpand: true, xAlign: Clutter.ActorAlign.START, }); this.durationLabel = new St.Label({ - text: durationText, + text: "00:00", xExpand: true, xAlign: Clutter.ActorAlign.END, }); - const sliderInterval = new Clutter.Interval({ - valueType: GObject.TYPE_DOUBLE, - initial: 0, - final: 1, - }); - - this.transition = new Clutter.PropertyTransition({ - propertyName: "value", - progressMode: Clutter.AnimationMode.LINEAR, - interval: sliderInterval, - duration: initLength, - repeatCount: 1, - }); - - this.transition.connect("marker-reached", (_, name: string) => { - this.elapsedLabel.text = name; - }); - this.slider.connect("drag-begin", () => { - if (this.transition.is_playing()) { + if (this.transition.is_playing() && this.disabled === false) { this.transition.pause(); this.dragPaused = true; } + return Clutter.EVENT_PROPAGATE; }); this.slider.connect("drag-end", () => { const ms = this.slider.value * this.transition.duration; + this.emit("seeked", Math.floor(ms * 1000)); if (this.dragPaused) { this.transition.advance(ms); @@ -75,7 +53,6 @@ class MenuSlider extends St.BoxLayout { this.dragPaused = false; } - this.emit("seeked", Math.floor(ms * 1000)); return Clutter.EVENT_PROPAGATE; }); @@ -83,66 +60,95 @@ class MenuSlider extends St.BoxLayout { return Clutter.EVENT_STOP; }); - this.updateMarkers(); - this.slider.add_transition("progress", this.transition); - - this.transition.skip(initLocation); + this.transition = new Clutter.PropertyTransition({ + propertyName: "value", + progressMode: Clutter.AnimationMode.LINEAR, + repeatCount: 1, + interval: new Clutter.Interval({ + valueType: GObject.TYPE_DOUBLE, + initial: 0, + final: 1, + }), + }); - if (initPaused) { - this.pauseTransition(); - } + this.transition.connect("marker-reached", (_, name: string) => { + this.elapsedLabel.text = name; + }); this.textBox.add_child(this.elapsedLabel); this.textBox.add_child(this.durationLabel); this.add_child(this.textBox); this.add_child(this.slider); - } - - private updateMarkers() { - const noOfSecs = Math.floor(this.transition.duration / 1000); - const markers = this.transition.list_markers(-1); - for (const marker of markers) { - this.transition.remove_marker(marker); - } + this.slider.add_transition("progress", this.transition); + this.setDisabled(true); + } - for (let i = 0; i <= noOfSecs; i++) { - const ms = i * 1000; - const elapsedText = msToHHMMSS(ms); - this.transition.add_marker_at_time(elapsedText, ms); - } + public updateSlider(position: number, length: number) { + this.setLength(length); + this.setPosition(position); } public setPosition(position: number) { position = position / 1000; - this.transition.advance(position); - this.slider.value = position / this.transition.duration; this.elapsedLabel.text = msToHHMMSS(position); + this.slider.value = position / this.transition.duration; + this.transition.advance(position); } public setLength(length: number) { length = length / 1000; - this.transition.set_duration(length); this.durationLabel.text = msToHHMMSS(length); + this.slider.value = 0; + this.transition.set_duration(length); + this.transition.rewind(); + this.updateMarkers(); } public pauseTransition() { - this.transition.pause(); + if (this.disabled === false) { + this.transition.pause(); + } } public resumeTransition() { - this.transition.start(); + if (this.disabled === false) { + this.transition.start(); + } } public setDisabled(disabled: boolean) { - this.slider.reactive = !disabled; + this.disabled = disabled; + this.slider.reactive = disabled === false; this.elapsedLabel.text = disabled ? "00:00" : this.elapsedLabel.text; this.durationLabel.text = disabled ? "00:00" : this.durationLabel.text; - this.transition.set_duration(disabled ? 0 : this.transition.duration); - this.updateMarkers(); + this.opacity = disabled ? 127 : 255; + + if (disabled) { + this.transition.set_duration(1); + this.transition.stop(); + this.slider.value = 0; + } else { + this.updateMarkers(); + } + } + + private updateMarkers() { + const noOfSecs = Math.floor(this.transition.duration / 1000); + const markers = this.transition.list_markers(-1); + + for (const marker of markers) { + this.transition.remove_marker(marker); + } + + for (let i = 0; i <= noOfSecs; i++) { + const ms = i * 1000; + const elapsedText = msToHHMMSS(ms); + this.transition.add_marker_at_time(elapsedText, ms); + } } } diff --git a/src/helpers/PanelButton.ts b/src/helpers/PanelButton.ts index 94a1d6f..02f0a50 100644 --- a/src/helpers/PanelButton.ts +++ b/src/helpers/PanelButton.ts @@ -16,7 +16,7 @@ import { KeysOf } from "../types/misc.js"; import { debugLog, handleError } from "../utils/misc.js"; import { getAppByIdAndEntry, getImage } from "../utils/shell_only.js"; import { PlayerProxyProperties } from "../types/dbus.js"; -import { ControlIconOptions } from "../types/enums/shell_only.js"; +import { ControlIconOptions, PanelControlIconOptions, MenuControlIconOptions } from "../types/enums/shell_only.js"; import { LabelTypes, PanelElements, @@ -25,8 +25,6 @@ import { PlaybackStatus, WidgetFlags, } from "../types/enums/general.js"; -import { PanelControlIconOptions } from "../types/enums/shell_only.js"; -import { MenuControlIconOptions } from "../types/enums/shell_only.js"; Gio._promisify(GdkPixbuf.Pixbuf, "new_from_stream_async", "new_from_stream_finish"); @@ -50,7 +48,7 @@ class PanelButton extends PanelMenu.Button { private menuPlayersTextBoxIcon: St.Icon; private menuPlayersTextBoxLabel: St.Label; private menuPlayersTextBoxPin: St.Icon; - private menuPlayersIcons: St.BoxLayout; + private menuPlayerIcons: St.BoxLayout; private menuLabelTitle: InstanceType; private menuLabelArtist: InstanceType; @@ -70,9 +68,7 @@ class PanelButton extends PanelMenu.Button { this.initActions(); this.menu.box.add_style_class_name("popup-menu-container"); - this.connect("destroy", () => { - this.removeProxyListeners(); - }); + this.connect("destroy", this.onDestroy.bind(this)); } public updateProxy(playerProxy: PlayerProxy) { @@ -89,8 +85,6 @@ class PanelButton extends PanelMenu.Button { } public updateWidgets(flags: WidgetFlags) { - debugLog(`Updating widgets with flags ${Object.keys(WidgetFlags).filter((key) => flags & WidgetFlags[key])}`); - if (this.buttonBox == null) { this.buttonBox = new St.BoxLayout({ styleClass: "panel-button-box", @@ -235,21 +229,21 @@ class PanelButton extends PanelMenu.Button { const players = this.extension.getPlayers(); - if (players.length > 1 && this.menuPlayersIcons == null) { - this.menuPlayersIcons = new St.BoxLayout({ + if (players.length > 1 && this.menuPlayerIcons == null) { + this.menuPlayerIcons = new St.BoxLayout({ styleClass: "popup-menu-player-icons", }); - } else if (players.length === 1 && this.menuPlayersIcons != null) { - this.menuPlayers.remove_child(this.menuPlayersIcons); + } else if (players.length === 1 && this.menuPlayerIcons != null) { + this.menuPlayers.remove_child(this.menuPlayerIcons); } else { - this.menuPlayersIcons?.remove_all_children(); + this.menuPlayerIcons?.remove_all_children(); } const isPinned = this.playerProxy.isPlayerPinned(); this.menuPlayersTextBoxPin.opacity = isPinned ? 255 : 160; - if (this.menuPlayersIcons != null) { - this.menuPlayersIcons.opacity = isPinned ? 160 : 255; + if (this.menuPlayerIcons != null) { + this.menuPlayerIcons.opacity = isPinned ? 160 : 255; } for (let i = 0; i < players.length; i++) { @@ -263,7 +257,10 @@ class PanelButton extends PanelMenu.Button { if (players.length > 1) { this.menuPlayersTextBoxIcon.gicon = null; - this.menuPlayersTextBox.remove_child(this.menuPlayersTextBoxIcon); + + if (this.menuPlayersTextBoxIcon?.get_parent() != null) { + this.menuPlayersTextBox.remove_child(this.menuPlayersTextBoxIcon); + } this.menuPlayersTextBoxLabel.xAlign = Clutter.ActorAlign.END; this.menuPlayersTextBoxLabel.xExpand = true; @@ -276,7 +273,9 @@ class PanelButton extends PanelMenu.Button { this.menuPlayersTextBoxLabel.xAlign = Clutter.ActorAlign.START; this.menuPlayersTextBoxLabel.xExpand = true; - this.menuPlayersTextBox.remove_child(this.menuPlayersTextBoxPin); + if (this.menuPlayersTextBoxPin.get_parent() != null) { + this.menuPlayersTextBox.remove_child(this.menuPlayersTextBoxPin); + } } } @@ -305,7 +304,7 @@ class PanelButton extends PanelMenu.Button { icon.add_action(tapAction); } - this.menuPlayersIcons.add_child(icon); + this.menuPlayerIcons.add_child(icon); } } @@ -317,8 +316,8 @@ class PanelButton extends PanelMenu.Button { this.menuPlayers.add_child(this.menuPlayersTextBox); } - if (this.menuPlayersIcons && this.menuPlayersIcons.get_parent() == null) { - this.menuPlayers.add_child(this.menuPlayersIcons); + if (this.menuPlayerIcons && this.menuPlayerIcons.get_parent() == null) { + this.menuPlayers.add_child(this.menuPlayerIcons); } if (this.menuPlayers.get_parent() == null) { @@ -342,6 +341,9 @@ class PanelButton extends PanelMenu.Button { if (stream == null) { this.menuImage.content = null; this.menuImage.gicon = Gio.ThemedIcon.new("audio-x-generic-symbolic"); + this.menuImage.width = this.extension.labelWidth; + this.menuImage.height = this.extension.labelWidth; + this.menuImage.iconSize = this.extension.labelWidth; } else { // @ts-expect-error Types are wrong const pixbufPromise = GdkPixbuf.Pixbuf.new_from_stream_async(stream, null) as Promise; @@ -396,7 +398,7 @@ class PanelButton extends PanelMenu.Button { this.menuLabelTitle.label.xExpand = true; this.menuLabelArtist = new ScrollingLabel( - this.playerProxy.metadata["xesam:artist"]?.join(", ") ?? "Unknown artist", + this.playerProxy.metadata["xesam:artist"]?.join(", ") || "Unknown artist", this.extension.labelWidth, true, this.extension.scrollLabels, @@ -422,20 +424,25 @@ class PanelButton extends PanelMenu.Button { const length = this.playerProxy.metadata["mpris:length"]; if (this.menuSlider == null) { - this.menuSlider = new MenuSlider( - position, - length, - this.playerProxy.playbackStatus !== PlaybackStatus.PLAYING, - ); + this.menuSlider = new MenuSlider(); this.menuSlider.connect("seeked", (_, position) => { this.playerProxy.setPosition(this.playerProxy.metadata["mpris:trackid"], position); }); } - this.menuSlider.setPosition(position); - this.menuSlider.setLength(length); - this.menuSlider.setDisabled(this.playerProxy.canSeek === false || position == null); + if (position != null && length != null && length > 0) { + this.menuSlider.setDisabled(false); + this.menuSlider.updateSlider(position, length); + + if (this.playerProxy.playbackStatus === PlaybackStatus.PLAYING) { + this.menuSlider.resumeTransition(); + } else { + this.menuSlider.pauseTransition(); + } + } else { + this.menuSlider.setDisabled(true); + } if (this.menuSlider.get_parent() == null) { this.menuBox.insert_child_above(this.menuSlider, this.menuLabels); @@ -559,7 +566,7 @@ class PanelButton extends PanelMenu.Button { private addButtonLabel(index: number) { const label = new ScrollingLabel( - this.getLabelText(), + this.getButtonLabelText(), this.extension.labelWidth, this.extension.isFixedLabelWidth, this.extension.scrollLabels, @@ -691,14 +698,14 @@ class PanelButton extends PanelMenu.Button { } } - private getLabelText() { + private getButtonLabelText() { const labelTextElements = []; for (const labelElement of this.extension.labelsOrder) { if (LabelTypes[labelElement] === LabelTypes.TITLE) { labelTextElements.push(this.playerProxy.metadata["xesam:title"]); } else if (LabelTypes[labelElement] === LabelTypes.ARTIST) { - labelTextElements.push(this.playerProxy.metadata["xesam:artist"]?.join(", ") ?? "Unknown artist"); + labelTextElements.push(this.playerProxy.metadata["xesam:artist"]?.join(", ") || "Unknown artist"); } else if (LabelTypes[labelElement] === LabelTypes.ALBUM) { labelTextElements.push(this.playerProxy.metadata["xesam:album"]); } else if (LabelTypes[labelElement] === LabelTypes.DISC_NUMBER) { @@ -912,6 +919,41 @@ class PanelButton extends PanelMenu.Button { break; } } + + private onDestroy() { + this.buttonIcon?.destroy(); + this.buttonLabel?.destroy(); + this.buttonControls?.destroy(); + this.buttonBox?.destroy(); + + this.buttonIcon = null; + this.buttonLabel = null; + this.buttonControls = null; + this.buttonBox = null; + + this.menuBox = null; + this.menuPlayers = null; + this.menuImage = null; + this.menuLabels = null; + this.menuSlider = null; + this.menuControls = null; + + this.menuPlayersTextBox = null; + this.menuPlayersTextBoxIcon = null; + this.menuPlayersTextBoxLabel = null; + this.menuPlayersTextBoxPin = null; + this.menuPlayerIcons = null; + + this.menuLabelTitle = null; + this.menuLabelArtist = null; + + if (this.doubleTapSourceId != null) { + GLib.source_remove(this.doubleTapSourceId); + this.doubleTapSourceId = null; + } + + this.removeProxyListeners(); + } } const GPanelButton = GObject.registerClass( diff --git a/src/helpers/PlayerProxy.ts b/src/helpers/PlayerProxy.ts index 38f2788..5578c77 100644 --- a/src/helpers/PlayerProxy.ts +++ b/src/helpers/PlayerProxy.ts @@ -1,7 +1,7 @@ import Gio from "gi://Gio?version=2.0"; import { MPRIS_PLAYER_IFACE_NAME, MPRIS_OBJECT_PATH, PlaybackStatus, LoopStatus } from "../types/enums/general.js"; -import { debugLog, errorLog, handleError } from "../utils/misc.js"; +import { errorLog, handleError } from "../utils/misc.js"; import { createDbusProxy } from "../utils/shell_only.js"; import { KeysOf } from "../types/misc.js"; import { @@ -13,6 +13,7 @@ import { PlayerProxyProperties, PropertiesInterface, } from "../types/dbus.js"; +import GLib from "gi://GLib?version=2.0"; type PlayerProxyChangeListeners = Map< KeysOf, @@ -25,6 +26,7 @@ export default class PlayerProxy { private mprisPlayerProxy: MprisPlayerInterface; private propertiesProxy: PropertiesInterface; private changeListeners: PlayerProxyChangeListeners; + private pollSourceId: number; public busName: string; public isInvalid: boolean; @@ -69,8 +71,7 @@ export default class PlayerProxy { this.propertiesProxy.connectSignal( "PropertiesChanged", - (proxy: unknown, senderName: string, [, changedProperties, invalidatedProperties]) => { - debugLog(invalidatedProperties); + (proxy: unknown, senderName: string, [, changedProperties]) => { for (const [property, value] of Object.entries(changedProperties)) { this.callOnChangedListeners(property as KeysOf, value.recursiveUnpack()); } @@ -81,6 +82,7 @@ export default class PlayerProxy { this.onChanged("Identity", this.validatePlayer.bind(this)); this.onChanged("DesktopEntry", this.validatePlayer.bind(this)); this.validatePlayer(); + this.pollTillInitialized(); return true; } @@ -99,14 +101,48 @@ export default class PlayerProxy { return this.isPinned; } + /** + * Some players don't set the initial position and metadata immediately on startup + */ + private pollTillInitialized() { + const timeout = 2000; + const interval = 250; + + let count = Math.ceil(timeout / interval); + + this.pollSourceId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, interval, () => { + count--; + + const positionPromise = this.propertiesProxy.GetAsync(MPRIS_PLAYER_IFACE_NAME, "Position"); + const metadataPromise = this.propertiesProxy.GetAsync(MPRIS_PLAYER_IFACE_NAME, "Metadata"); + + Promise.all([positionPromise, metadataPromise]) + .then(([positionVariant, metadataVariant]) => { + const unpackedPosition = positionVariant[0].recursiveUnpack() as number; + const unpackedMetadata = + metadataVariant[0].recursiveUnpack() as MprisPlayerInterfaceMetadataUnpacked; + + if ((unpackedPosition > 0 && unpackedMetadata["mpris:length"] > 0) || count <= 0) { + this.mprisPlayerProxy.set_cached_property("Position", positionVariant[0]); + this.mprisPlayerProxy.set_cached_property("Metadata", metadataVariant[0]); + this.callOnChangedListeners("Metadata", unpackedMetadata); + GLib.source_remove(this.pollSourceId); + } + }) + .catch(() => { + GLib.source_remove(this.pollSourceId); + }); + + return GLib.SOURCE_CONTINUE; + }); + } + private validatePlayer() { const isValidName = this.mprisProxy.Identity || this.mprisProxy.DesktopEntry; const isValidMetadata = this.metadata && this.metadata["xesam:title"]; this.isInvalid = !isValidName || !isValidMetadata; this.callOnChangedListeners("IsInvalid", this.isInvalid); - - debugLog("Player", this.busName, "is", this.isInvalid ? "invalid" : "valid"); } private unpackMetadata(metadata: MprisPlayerInterfaceMetadata) { @@ -123,7 +159,6 @@ export default class PlayerProxy { property: T, value: PlayerProxyProperties[T], ) { - debugLog("Player", this.busName, "changed", property, "to", value); const listeners = this.changeListeners.get(property); if (listeners == null) { @@ -169,7 +204,9 @@ export default class PlayerProxy { .then((result) => { return result[0].get_int64(); }) - .catch(handleError); + .catch(() => { + return null; + }); } get minimumRate(): number { diff --git a/src/helpers/ScrollingLabel.ts b/src/helpers/ScrollingLabel.ts index ad5394e..d8a3c9b 100644 --- a/src/helpers/ScrollingLabel.ts +++ b/src/helpers/ScrollingLabel.ts @@ -37,7 +37,7 @@ class ScrollingLabel extends St.ScrollView { }); const signalId = this.label.connect("show", () => { - if (this.label.allocation == null || this.label.width === 0) { + if (this.label.allocation == null || this.label.visible === false) { return; } diff --git a/src/types/dbus.ts b/src/types/dbus.ts index 7a51232..24ac317 100644 --- a/src/types/dbus.ts +++ b/src/types/dbus.ts @@ -61,7 +61,7 @@ export interface PlayerProxyDBusProperties { LoopStatus: LoopStatus; Rate: number; Shuffle: boolean; - Metadata: MprisPlayerInterfaceMetadata; + Metadata: MprisPlayerInterfaceMetadata | MprisPlayerInterfaceMetadataUnpacked; Volume: number; Position: number; MinimumRate: number;