Update mpris player for new version

This commit is contained in:
2024-07-07 11:23:14 +07:00
parent 668e3f42f4
commit d64195b40c
9 changed files with 312 additions and 371 deletions
+2 -4
View File
@@ -1,8 +1,7 @@
import { Bar } from "./layouts/bar.js";
import { Notifications } from "./layouts/notifications.js";
// import { OSD } from "./layouts/osd.js";
// import { Powermenu } from "./layouts/powermenu.js";
// import { Quicksettings } from "./layouts/quicksettings.js";
import { Quicksettings } from "./layouts/quicksettings.js";
import Gtk from "gi://Gtk?version=3.0";
import Gdk from "gi://Gdk";
@@ -23,9 +22,8 @@ App.config({
style: `${App.configDir}/style.css`,
windows: [
...forMonitors(Bar),
// Quicksettings(),
Quicksettings(),
Notifications(),
// OSD(),
// Powermenu(),
],
});
+31 -11
View File
@@ -1,18 +1,38 @@
{pkgs, ...}: let
# covercolors = pkgs.stdenv.mkDerivation {
# name = "covercolors";
# dontUnpack = true;
{
pkgs,
lib,
...
}: let
covercolors = pkgs.stdenv.mkDerivation {
name = "covercolors";
dontUnpack = true;
propagatedBuildInputs = [
(pkgs.python3.withPackages (pyPkgs:
with pyPkgs; [
material-color-utilities
pillow
]))
];
installPhase = "install -Dm755 ${./covercolors.py} $out/bin/covercolors";
};
# ags = pkgs.stdenv.mkDerivation rec {
# name = "ags";
# nativeBuildInputs = with pkgs; [makeWrapper];
# propagatedBuildInputs = [
# (pkgs.python3.withPackages (pyPkgs:
# with pyPkgs; [
# material-color-utilities
# pillow
# ]))
# covercolors
# ];
# installPhase = "install -Dm755 ${./covercolors.py} $out/bin/covercolors";
# dontUnpack = true;
# installPhase = "
# wrapProgram ${pkgs.ags}/bin/ags --prefix PATH : '${lib.makeBinPath propagatedBuildInputs}'
# ";
# };
ags = pkgs.ags.overrideAttrs (oldAttrs: {
runtimeDependencies = [covercolors];
});
systemdTarget = "graphical-session.target";
in {
# TODO: Remove this after testing
home.packages = [ags];
systemd.user.services.ags = {
Unit = {
Description = " A customizable and extensible shell ";
@@ -23,7 +43,7 @@ in {
Service = {
Type = "simple";
ExecStart = "${pkgs.ags}/bin/ags";
ExecStart = "${ags}/bin/ags";
Restart = "always";
};
+1 -1
View File
@@ -38,7 +38,7 @@ export const Notifications = () =>
min-width: 600px;
margin: 10px;
padding: 12px;
border-radius: 40px;
border-radius: 20px;
`,
children:
/** @type {any} */
+108 -104
View File
@@ -1,111 +1,115 @@
import * as audio from "../modules/audio.js";
import * as brightness from "../modules/brightness.js";
import * as network from "../modules/network.js";
import * as bluetooth from "../modules/bluetooth.js";
import * as darkmode from "../modules/darkmode.js";
import * as nightmode from "../modules/nightmode.js";
// import * as audio from "../modules/audio.js";
// import * as brightness from "../modules/brightness.js";
// import * as network from "../modules/network.js";
// import * as bluetooth from "../modules/bluetooth.js";
// import * as darkmode from "../modules/darkmode.js";
// import * as nightmode from "../modules/nightmode.js";
import * as mpris from "../modules/mpris.js";
import { opened, Arrow } from "../services/quicksettings.js";
import { FontIcon, PopupOverlay } from "../misc.js";
import PopupWindow from "../misc/popup.js";
// import { opened, Arrow } from "../services/quicksettings.js";
// import { FontIcon, PopupOverlay } from "../misc.js";
import { Window, Revealer, Icon, Box, Button, Label } from 'resource:///com/github/Aylur/ags/widget.js'
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'
const Submenu = ({ menuName, icon, title, contentType }) =>
Revealer({
transition: "slide_down",
connections: [[opened, (r) => (r.reveal_child = menuName === opened.value)]],
child: Box({
className: "qs-submenu surface",
vertical: true,
children: [
Box({ className: "qs-sub-title accent", children: [icon, Label({ label: title, className: "bold f16" })] }),
contentType({ className: "qs-sub-content", hexpand: true }),
],
}),
});
const VolumeBox = () =>
Box({
vertical: true,
children: [
Box({
className: "qs-slider",
children: [
Button({
child: audio.SpeakerIndicator(),
onClicked: () => execAsync("pactl set-sink-mute @DEFAULT_SINK@ toggle"),
}),
audio.SpeakerSlider({ hexpand: true }),
audio.SpeakerPercentLabel(),
Arrow({ name: "stream-selector" }),
],
}),
Submenu({
menuName: "stream-selector",
icon: Icon("audio-volume-medium-symbolic"),
title: "Audio Stream",
contentType: audio.StreamSelector,
}),
],
});
const BrightnessBox = () =>
Box({
className: "qs-slider",
children: [
brightness.Indicator(),
brightness.BrightnessSlider({ hexpand: true }),
brightness.PercentLabel(),
Box({ className: "qs-icon", css: "margin-right: 18px;" }),
],
});
const mprisService = await Service.import("mpris");
// const Submenu = ({ menuName, icon, title, contentType }) =>
// Revealer({
// transition: "slide_down",
// connections: [
// [opened, (r) => (r.reveal_child = menuName === opened.value)],
// ],
// child: Box({
// className: "qs-submenu surface",
// vertical: true,
// children: [
// Box({
// className: "qs-sub-title accent",
// children: [icon, Label({ label: title, className: "bold f16" })],
// }),
// contentType({ className: "qs-sub-content", hexpand: true }),
// ],
// }),
// });
//
// const VolumeBox = () =>
// Box({
// vertical: true,
// children: [
// Box({
// className: "qs-slider",
// children: [
// Button({
// child: audio.SpeakerIndicator(),
// onClicked: () =>
// execAsync("pactl set-sink-mute @DEFAULT_SINK@ toggle"),
// }),
// audio.SpeakerSlider({ hexpand: true }),
// audio.SpeakerPercentLabel(),
// Arrow({ name: "stream-selector" }),
// ],
// }),
// Submenu({
// menuName: "stream-selector",
// icon: Icon("audio-volume-medium-symbolic"),
// title: "Audio Stream",
// contentType: audio.StreamSelector,
// }),
// ],
// });
//
// const BrightnessBox = () =>
// Box({
// className: "qs-slider",
// children: [
// brightness.Indicator(),
// brightness.BrightnessSlider({ hexpand: true }),
// brightness.PercentLabel(),
// Box({ className: "qs-icon", css: "margin-right: 18px;" }),
// ],
// });
export const Quicksettings = () =>
Window({
PopupWindow({
name: "quicksettings",
popup: true,
visible: false,
anchor: ["top", "right", "bottom", "left"],
child: PopupOverlay(
"quicksettings",
"top right",
Box({
vertical: true,
className: "bgcont qs-container",
children: [
VolumeBox(),
BrightnessBox(),
Box({
children: [network.Toggle({}), bluetooth.Toggle({})],
}),
Submenu({
menuName: "network",
icon: Icon("network-wireless-symbolic"),
title: "Network",
contentType: network.Selection,
}),
Submenu({
menuName: "bluetooth",
icon: Icon("bluetooth-symbolic"),
title: "Bluetooth",
contentType: bluetooth.Devices,
}),
Box({
children: [darkmode.DarkToggle(), nightmode.NightToggle()],
}),
Box({
children: [audio.AppMixerToggle(), audio.MuteToggle()],
}),
Submenu({
menuName: "app-mixer",
icon: FontIcon({ icon: "" }),
title: "App Mixer",
contentType: audio.AppMixer,
}),
mpris.MprisPlayer(),
],
})
),
exclusivity: "exclusive",
transition: "slide_down",
layout: "top-right",
child: Widget.Box({
vertical: true,
className: "bgcont qs-container",
children:
mprisService
.bind("players")
.as((x) => x.map((player) => mpris.MprisPlayer({ player }))),
// children: [
// VolumeBox(),
// BrightnessBox(),
// Widget.Box({
// children: [network.Toggle({}), bluetooth.Toggle({})],
// }),
// Submenu({
// menuName: "network",
// icon: "network-wireless-symbolic",
// title: "Network",
// contentType: network.Selection,
// }),
// Submenu({
// menuName: "bluetooth",
// icon: "bluetooth-symbolic",
// title: "Bluetooth",
// contentType: bluetooth.Devices,
// }),
// Box({
// children: [darkmode.DarkToggle(), nightmode.NightToggle()],
// }),
// Box({
// children: [audio.AppMixerToggle(), audio.MuteToggle()],
// }),
// Submenu({
// menuName: "app-mixer",
// icon: FontIcon({ icon: "" }),
// title: "App Mixer",
// contentType: audio.AppMixer,
// }),
// ],
}),
});
+21 -121
View File
@@ -1,126 +1,26 @@
import Service from 'resource:///com/github/Aylur/ags/service.js';
import { Box, Stack, Button, Icon } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
/** @param {import("types/service/mpris").MprisPlayer} player */
export const getMaterialColors = (player) => {
const ret = Variable({
primary: "#222222",
onPrimary: "#ffffff",
background: "#222222",
onBackground: "#ffffff",
coverPath: "",
});
class MaterialcolorsService extends Service {
static {
Service.register(this);
}
getColors(url) {
if (url) {
timeout(100, () => {
execAsync(["covercolors", url])
.then((colors) => {
const col = JSON.parse(colors);
if (!col) return;
this._colors = col;
this.emit("changed");
})
.catch(print);
});
}
}
constructor() {
super();
this._colors = {
primary: "#222222",
onPrimary: "#ffffff",
background: "#222222",
onBackground: "#ffffff",
};
Mpris.connect("changed", () => {
this._mprisPlayer = Mpris.getPlayer("YoutubeMusic");
this._coverPath = this._mprisPlayer?.coverPath;
this.getColors(this.coverPath);
this.emit("changed");
player.bind("cover_path").as((cover) => {
Utils.timeout(100, () => {
Utils.execAsync(["covercolors", cover])
.then((colors) => {
const col = JSON.parse(colors);
if (!col) return;
col.coverPath = cover;
ret.setValue(col);
})
.catch(print);
});
}
get colors() {
return this._colors;
}
get coverPath() {
return this._coverPath;
}
}
export const Materialcolors = new MaterialcolorsService();
export const PlayPause = ({ player, ...props }) =>
Button({
child: Stack({
items: [
["Playing", Icon("media-playback-pause-symbolic")],
["Paused", Icon("media-playback-start-symbolic")],
["Stopped", Icon("media-playback-start-symbolic")],
],
connections: [
[
Mpris,
(stack) => {
const mpris = Mpris.getPlayer(player);
stack.shown = mpris?.playBackStatus ?? "Stopped";
},
],
],
}),
onClicked: () => Mpris.getPlayer(player)?.playPause(),
connections: [
[
Materialcolors,
(icon) => {
icon.setCss(`
background-color: ${Materialcolors.colors.primary};
color: ${Materialcolors.colors.onPrimary};
`);
},
],
[
Mpris,
(button) => {
const mpris = Mpris.getPlayer(player);
if (!mpris || !mpris.canPlay) return button.hide();
button.show();
},
],
],
...props,
});
export const CoverArt = (props) =>
Box({
...props,
className: `mpris-cover-art ${props.className}`,
connections: [
[
Materialcolors,
(box) => {
box.setCss(`
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.4) 30%, ${Materialcolors.colors.primary}), url("${Materialcolors.coverPath}"); \
color: ${Materialcolors.colors.onBackground};
`);
},
],
...(props.connections ?? []),
],
});
return ret;
};
export const BackgroundBox = (props = {}) =>
Box({
...props,
connections: [
[
Materialcolors,
(box) => {
box.setCss(`
color: ${Materialcolors.colors.onBackground};
`);
},
],
],
});
+131 -111
View File
@@ -1,151 +1,179 @@
import { BackgroundBox, CoverArt, PlayPause } from "./materialcolors.js";
import { addElipsis } from "../misc.js";
import { getMaterialColors } from "./materialcolors.js";
import { Box, Button, Slider, Icon, CenterBox, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'
import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js';
const mpris = await Service.import("mpris");
const PlayerIcon = ({ player, symbolic = false, ...props }) =>
Icon({
...props,
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/icon").IconProps} props */
const PlayerIcon = ({ player, ...props }) =>
Widget.Icon({
size: 24,
hpack: "start",
vpack: "start",
connections: [
[
Mpris,
(icon) => {
const name = `${Mpris.getPlayer(player)?.entry}${symbolic ? "-symbolic" : ""}`;
lookUpIcon(name) ? (icon.icon_name = name) : (icon.icon_name = "audio-x-generic-symbolic");
},
],
],
tooltipText: player.identity || "",
icon: player.bind("entry").transform((entry) => {
const name = `${entry}-symbolic`;
return Utils.lookUpIcon(name)
? name
: Utils.lookUpIcon(entry)
? entry
: "audio-x-generic-symbolic";
}),
...props,
});
const TitleLabel = ({ player, ...props } = {}) =>
Label({
...props,
truncate: "end",
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/label").LabelProps} props */
const TitleLabel = ({ player, ...props }) =>
Widget.Label({
wrap: true,
connections: [
[
Mpris,
(label) => {
label.label = addElipsis(Mpris.getPlayer(player)?.trackTitle || "", 25);
},
],
],
});
const ArtistLabel = ({ player, ...props }) =>
Label({
...props,
truncate: "end",
connections: [
[
Mpris,
(label) => {
label.label = addElipsis(Mpris.getPlayer(player)?.trackArtists.join(", ") || "", 25);
},
],
],
hpack: "start",
label: player.bind("track_title"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/label").LabelProps} props */
const ArtistLabel = ({ player, ...props }) =>
Widget.Label({
wrap: true,
truncate: "end",
hpack: "start",
label: player.bind("track_artists").transform((x) => x.join(", ")),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/button").ButtonProps} props */
export const PlayPause = ({ player, ...props }) =>
Widget.Button({
child: Widget.Icon({
icon: player.bind("play_back_status").as(
(x) =>
({
Playing: "media-playback-pause-symbolic",
Paused: "media-playback-start-symbolic",
Stopped: "media-playback-start-symbolic",
})[x],
),
}),
onClicked: () => player.playPause(),
visible: player.bind("can_play"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/button").ButtonProps} props */
const PreviousButton = ({ player, ...props }) =>
Button({
child: Icon("media-skip-backward-symbolic"),
onClicked: () => Mpris.getPlayer(player)?.previous(),
connections: [
[
Mpris,
(button) => {
const mpris = Mpris.getPlayer(player);
if (!mpris || !mpris.canGoPrev) return button.hide();
button.show();
},
],
],
Widget.Button({
child: Widget.Icon({ icon: "media-skip-backward-symbolic" }),
onClicked: () => player.previous(),
visible: player.bind("can_go_prev"),
...props,
});
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/button").ButtonProps} props */
const NextButton = ({ player, ...props }) =>
Button({
child: Icon("media-skip-forward-symbolic"),
onClicked: () => Mpris.getPlayer(player)?.next(),
connections: [
[
Mpris,
(button) => {
const mpris = Mpris.getPlayer(player);
if (!mpris || !mpris.canGoNext) return button.hide();
button.show();
},
],
],
Widget.Button({
child: Widget.Icon({ icon: "media-skip-forward-symbolic" }),
onClicked: () => player.next(),
visible: player.bind("can_go_next"),
...props,
});
const PositionSlider = ({ player, ...props }) => {
const update = (slider) => {
if (slider._dragging) return;
const mpris = Mpris.getPlayer(player);
// Only set opacity and not change the visible bool to keep it expanded.
slider.opacity = mpris?.length > 0 ? 1 : 0;
if (mpris && mpris.length > 0) slider.adjustment.value = mpris.position / mpris.length;
};
return Slider({
drawValue: false,
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("types/widgets/slider").SliderProps} props */
const PositionSlider = ({ player, ...props }) =>
Widget.Slider({
className: "mpris-position-slider",
onChange: (value) => {
const mpris = Mpris.getPlayer(player);
if (mpris && mpris.length >= 0) Mpris.getPlayer(player).position = mpris.length * value;
drawValue: false,
onChange: ({ value }) => {
player.position = value * player.length;
},
visible: player.bind("length").as((l) => l > 0),
setup: (self) => {
function update() {
const value = player.position / player.length;
self.value = value > 0 ? value : 0;
}
self.hook(player, update);
self.hook(player, update, "position");
self.poll(1000, update);
},
connections: [
[Mpris, update],
[1000, update],
],
...props,
});
};
export const MprisPlayer = ({ player = "YoutubeMusic", ...props } = {}) =>
CoverArt({
// export const currentPlayer = Utils.watch(
// null,
// [
// [mpris, "player-added"],
// [mpris, "player-changed"],
// ],
// /** @param {any} name */
// (name) => mpris.getPlayer(name),
// );
/** @param {{player?: import("types/service/mpris").MprisPlayer | null} & import("types/widgets/box").BoxProps} props */
export const MprisPlayer = ({ player, ...props }) => {
if (!player) return Widget.Box({ visible: false });
const colors = getMaterialColors(player);
return Widget.Box({
visible: player.bind("play_back_status").as((x) => x !== "Stopped"),
className: `mpris-cover-art ${props.className}`,
css: colors.bind().as(
(x) => `
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.4) 30%, ${x.primary}), url("${x.coverPath}"); \
color: ${x.onBackground};
`,
),
vexpand: false,
children: [
CenterBox({
Widget.Box({
vertical: true,
hexpand: true,
children: [
BackgroundBox({
Widget.Box({
vertical: true,
vexpand: true,
css: colors.bind().as((x) => `color: ${x.onBackground}`),
child: PlayerIcon({ player }),
}),
Box({
Widget.Box({
hexpand: true,
children: [
BackgroundBox({
Widget.Box({
css: colors.bind().as((x) => `color: ${x.onBackground}`),
vertical: true,
vpack: "center",
hexpand: true,
children: [
TitleLabel({ player, css: "font-weight: 600; font-size: 19px;" }),
ArtistLabel({ player, css: "font-weight: 400; font-size: 17px;" }),
TitleLabel({
player,
css: "font-weight: 600; font-size: 19px;",
}),
ArtistLabel({
player,
css: "font-weight: 400; font-size: 17px;",
}),
],
}),
Box({
children: [PlayPause({ player, hpack: "end", className: "mpris-play" })],
Widget.Box({
children: [
PlayPause({
player,
hpack: "end",
className: "mpris-play",
css: colors.bind().as(
(x) => `
background-color: ${x.primary};
color: ${x.onPrimary};
`,
),
}),
],
}),
],
}),
BackgroundBox({
Widget.Box({
css: colors.bind().as((x) => `color: ${x.onBackground}`),
vpack: "end",
children: [
Box({
Widget.Box({
hexpand: true,
children: [
PreviousButton({ player, css: "margin-left: 16px;" }),
@@ -158,14 +186,6 @@ export const MprisPlayer = ({ player = "YoutubeMusic", ...props } = {}) =>
],
}),
],
connections: [
[
Mpris,
(widget) => {
const mpris = Mpris.getPlayer(player);
widget.visible = mpris !== null && mpris.playBackStatus !== "Stopped";
},
],
],
...props,
});
};
+1 -1
View File
@@ -1,4 +1,4 @@
{pkgs, inputs, ...}: {
{pkgs, ...}: {
services.xserver = {
enable = true;
displayManager.gdm = {
+1 -1
View File
@@ -1,4 +1,4 @@
{pkgs, inputs, ...}: {
{pkgs, ...}: {
services.greetd = {
enable = true;
settings = {
+16 -17
View File
@@ -3,20 +3,19 @@
lib,
...
}: let
guesspath =
pkgs.stdenv.mkDerivation rec {
name = "guesspath";
nativeBuildInputs = with pkgs; [makeWrapper];
propagatedBuildInputs = with pkgs; [
python3Packages.guessit
transmission_4
];
dontUnpack = true;
installPhase = "
install -Dm755 ${./guesspath.sh} $out/bin/guesspath
wrapProgram $out/bin/guesspath --prefix PATH : '${lib.makeBinPath propagatedBuildInputs}'
";
};
guesspath = pkgs.stdenv.mkDerivation rec {
name = "guesspath";
nativeBuildInputs = with pkgs; [makeWrapper];
propagatedBuildInputs = with pkgs; [
python3Packages.guessit
transmission_4
];
dontUnpack = true;
installPhase = "
install -Dm755 ${./guesspath.sh} $out/bin/guesspath
wrapProgram $out/bin/guesspath --prefix PATH : '${lib.makeBinPath propagatedBuildInputs}'
";
};
smartrss = pkgs.stdenv.mkDerivation rec {
name = "smartrss";
@@ -28,9 +27,9 @@
];
dontUnpack = true;
installPhase = "
install -Dm755 ${./smartrss.sh} $out/bin/smartrss
wrapProgram $out/bin/smartrss --prefix PATH : '${lib.makeBinPath propagatedBuildInputs}'
";
install -Dm755 ${./smartrss.sh} $out/bin/smartrss
wrapProgram $out/bin/smartrss --prefix PATH : '${lib.makeBinPath propagatedBuildInputs}'
";
};
in {
# Make it use predictable interface names starting with eth0