music controls: use pixbuf+cairo for cover art

- more reliable than gtk css
This commit is contained in:
end-4
2024-02-13 21:21:21 +07:00
parent 1577287624
commit 06245d13b0
+115 -62
View File
@@ -1,4 +1,4 @@
const { Gio, GLib } = imports.gi; const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
@@ -123,70 +123,123 @@ const TrackArtists = ({ player, ...rest }) => Label({
}, 'notify::track-artists'), }, 'notify::track-artists'),
}) })
const CoverArt = ({ player, ...rest }) => Box({ const CoverArt = ({ player, ...rest }) => {
...rest, const fallbackCoverArt = Box({ // Fallback
className: 'osd-music-cover', className: 'osd-music-cover-fallback',
children: [ homogeneous: true,
Widget.Overlay({ children: [Label({
child: Box({ // Fallback className: 'icon-material txt-gigantic txt-thin',
className: 'osd-music-cover-fallback', label: 'music_note',
homogeneous: true, })]
children: [Label({ });
className: 'icon-material txt-gigantic txt-thin', const coverArtDrawingArea = Widget.DrawingArea({ className: 'osd-music-cover-art' });
label: 'music_note', const coverArtDrawingAreaStyleContext = coverArtDrawingArea.get_style_context();
})] const realCoverArt = Box({
}), className: 'osd-music-cover-art',
overlays: [ // Real homogeneous: true,
Box({ children: [coverArtDrawingArea],
attribute: { attribute: {
'updateCover': (self) => { 'pixbuf': null,
// const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this 'showImage': (self, imagePath) => {
// Player closed const borderRadius = coverArtDrawingAreaStyleContext.get_property('border-radius', Gtk.StateFlags.NORMAL);
// Note that cover path still remains, so we're checking title const frameHeight = coverArtDrawingAreaStyleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
if (!player || player.trackTitle == "") { const frameWidth = coverArtDrawingAreaStyleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
self.css = `background-image: none;`; let imageHeight = frameHeight;
App.applyCss(`${App.configDir}/style.css`); let imageWidth = frameWidth;
return; // Get image dimensions
} execAsync(['identify', '-format', '{"w":%w,"h":%h}', imagePath])
.then((output) => {
const imageDimensions = JSON.parse(output);
const imageAspectRatio = imageDimensions.w / imageDimensions.h;
const displayedAspectRatio = imageWidth / imageHeight;
if (imageAspectRatio >= displayedAspectRatio) {
imageWidth = imageHeight * imageAspectRatio;
} else {
imageHeight = imageWidth / imageAspectRatio;
}
// Real stuff
// TODO: fix memory leak(?)
// if (self.attribute.pixbuf) {
// self.attribute.pixbuf.unref();
// self.attribute.pixbuf = null;
// }
self.attribute.pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imagePath, imageWidth, imageHeight);
const coverPath = player.coverPath; coverArtDrawingArea.set_size_request(frameWidth, frameHeight);
const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`; coverArtDrawingArea.connect("draw", (widget, cr) => {
if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete // Clip a rounded rectangle area
Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; }); cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
} cr.arc(frameWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
lastCoverPath = player.coverPath; cr.arc(frameWidth - borderRadius, frameHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
cr.arc(borderRadius, frameHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
cr.closePath();
cr.clip();
// Paint image as bg, centered
Gdk.cairo_set_source_pixbuf(cr, self.attribute.pixbuf,
frameWidth / 2 - imageWidth / 2,
frameHeight / 2 - imageHeight / 2
);
cr.paint();
});
}).catch(print)
},
'updateCover': (self) => {
// const player = Mpris.getPlayer(); // Maybe no need to re-get player.. can't remember why I had this
// Player closed
// Note that cover path still remains, so we're checking title
if (!player || player.trackTitle == "") {
self.css = `background-image: none;`;
App.applyCss(`${App.configDir}/style.css`);
return;
}
// If a colorscheme has already been generated, skip generation const coverPath = player.coverPath;
if (fileExists(stylePath)) { const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`;
Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; }); if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
App.applyCss(stylePath); // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
return; Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
} }
lastCoverPath = player.coverPath;
// Generate colors // If a colorscheme has already been generated, skip generation
execAsync(['bash', '-c', if (fileExists(stylePath)) {
`${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`]) // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
.then(() => { self.attribute.showImage(self, coverPath)
exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`) App.applyCss(stylePath);
exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`); return;
exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`); }
self.css = `background-image: url('${coverPath}');`;
App.applyCss(`${stylePath}`); // Generate colors
}) execAsync(['bash', '-c',
.catch(print); `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`])
}, .then(() => {
}, exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`)
className: 'osd-music-cover-art', exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`);
setup: (self) => self exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`);
.hook(player, (self) => { // self.css = `background-image: url('${coverPath}');`;
self.attribute.updateCover(self); Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
}, 'notify::cover-path') App.applyCss(`${stylePath}`);
, })
}) .catch(print);
] },
}) },
], setup: (self) => self
}) .hook(player, (self) => {
self.attribute.updateCover(self);
}, 'notify::cover-path')
,
});
return Box({
...rest,
className: 'osd-music-cover',
children: [
Widget.Overlay({
child: fallbackCoverArt,
overlays: [realCoverArt],
})
],
})
}
const TrackControls = ({ player, ...rest }) => Widget.Revealer({ const TrackControls = ({ player, ...rest }) => Widget.Revealer({
revealChild: false, revealChild: false,