Update the ags configuration

There is now a bargain bin gnome-shell quicksettings menu with basically
only media controls. This also takes care of notifications now.

TODO: Add the code for shutting down and restarting the computer with
the buttons in the quicksettings menu.
This commit is contained in:
caem 2024-05-30 19:45:14 +02:00
parent c409d58b19
commit 36ea0b0e39
Signed by: caem
GPG key ID: 69A830D03203405F
8 changed files with 693 additions and 147 deletions

View file

@ -0,0 +1,158 @@
/*
* This snippet is taken from the ags examples directory. It is licensed under GPLv3.
* More information available here: https://github.com/Aylur/ags/blob/main/LICENSE
* */
const mpris = await Service.import("mpris")
const players = mpris.bind("players")
const FALLBACK_ICON = "audio-x-generic-symbolic"
const PLAY_ICON = "media-playback-start-symbolic"
const PAUSE_ICON = "media-playback-pause-symbolic"
const PREV_ICON = "media-skip-backward-symbolic"
const NEXT_ICON = "media-skip-forward-symbolic"
/** @param {number} length */
function lengthStr(length) {
const min = Math.floor(length / 60)
const sec = Math.floor(length % 60)
const sec0 = sec < 10 ? "0" : ""
return `${min}:${sec0}${sec}`
}
/** @param {import('types/service/mpris').MprisPlayer} player */
function Player(player) {
const img = Widget.Box({
class_name: "img",
vpack: "start",
css: player.bind("cover_path").transform(p => `
background-image: url('${p}');
`),
})
const title = Widget.Label({
class_name: "title",
wrap: true,
hpack: "start",
label: player.bind("track_title"),
})
const artist = Widget.Label({
class_name: "artist",
wrap: true,
hpack: "start",
label: player.bind("track_artists").transform(a => a.join(", ")),
})
const positionSlider = Widget.Slider({
class_name: "position",
draw_value: false,
on_change: ({ 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)
},
})
const positionLabel = Widget.Label({
class_name: "position",
hpack: "start",
setup: self => {
const update = (_, time) => {
self.label = lengthStr(time || player.position)
self.visible = player.length > 0
}
self.hook(player, update, "position")
self.poll(1000, update)
},
})
const lengthLabel = Widget.Label({
class_name: "length",
hpack: "end",
visible: player.bind("length").transform(l => l > 0),
label: player.bind("length").transform(lengthStr),
})
const icon = Widget.Icon({
class_name: "icon",
hexpand: true,
hpack: "end",
vpack: "start",
tooltip_text: player.identity || "",
icon: player.bind("entry").transform(entry => {
const name = `${entry}-symbolic`
return Utils.lookUpIcon(name) ? name : FALLBACK_ICON
}),
})
const playPause = Widget.Button({
class_name: "play-pause",
on_clicked: () => player.playPause(),
visible: player.bind("can_play"),
child: Widget.Icon({
icon: player.bind("play_back_status").transform(s => {
switch (s) {
case "Playing": return PAUSE_ICON
case "Paused":
case "Stopped": return PLAY_ICON
}
}),
}),
})
const prev = Widget.Button({
on_clicked: () => player.previous(),
visible: player.bind("can_go_prev"),
child: Widget.Icon(PREV_ICON),
})
const next = Widget.Button({
on_clicked: () => player.next(),
visible: player.bind("can_go_next"),
child: Widget.Icon(NEXT_ICON),
})
return Widget.Box(
{ class_name: "player" },
img,
Widget.Box(
{
vertical: true,
hexpand: true,
},
Widget.Box([
title,
icon,
]),
artist,
Widget.Box({ vexpand: true }),
positionSlider,
Widget.CenterBox({
start_widget: positionLabel,
center_widget: Widget.Box([
prev,
playPause,
next,
]),
end_widget: lengthLabel,
}),
),
)
}
export function Media() {
return Widget.Box({
vertical: true,
css: "min-height: 2px; min-width: 2px;", // small hack to make it visible
visible: players.as(p => p.length > 0),
children: players.as(p => p.map(Player)),
})
}

View file

@ -0,0 +1,148 @@
/*
* This snippet is taken from the ags examples directory. It is licensed under GPLv3.
* More information available here: https://github.com/Aylur/ags/blob/main/LICENSE
*
* Modifications have been made.
* */
const notifications = await Service.import("notifications")
/** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */
function NotificationIcon({ app_entry, app_icon, image }) {
if (image) {
return Widget.Box({
css: `background-image: url("${image}");`
+ "background-size: contain;"
+ "background-repeat: no-repeat;"
+ "background-position: center;",
})
}
let icon = "dialog-information-symbolic"
if (Utils.lookUpIcon(app_icon))
icon = app_icon
if (app_entry && Utils.lookUpIcon(app_entry))
icon = app_entry
return Widget.Box({
child: Widget.Icon(icon),
})
}
/** @param {import('resource:///com/github/Aylur/ags/service/notifications.js').Notification} n */
function Notification(n) {
/* Setting the max width in gtk css is not supported for some reason
* so we have to split the length of the lines here by inserting newline
* characters where appropriate. */
let body_text = n.body;
for (let i = 0; i < body_text.length; i += 40) {
let left = body_text.substring(0, i);
let right = body_text.substring(i);
body_text = left + "\n" + right;
}
const icon = Widget.Box({
vpack: "start",
class_name: "icon",
child: NotificationIcon(n),
})
const title = Widget.Label({
class_name: "title",
xalign: 0,
justification: "left",
hexpand: true,
max_width_chars: 24,
truncate: "end",
wrap: true,
label: n.summary,
use_markup: true,
})
const body = Widget.Label({
class_name: "body",
hexpand: true,
use_markup: true,
xalign: 0,
justification: "left",
label: body_text,
wrap: true,
})
const actions = Widget.Box({
class_name: "actions",
children: n.actions.map(({ id, label }) => Widget.Button({
class_name: "action-button",
on_clicked: () => {
n.invoke(id)
n.dismiss()
},
hexpand: true,
child: Widget.Label(label),
})),
})
return Widget.EventBox(
{
attribute: { id: n.id },
on_primary_click: n.dismiss,
},
Widget.Box(
{
class_name: `notification ${n.urgency}`,
vertical: true,
},
Widget.Box([
icon,
Widget.Box(
{ vertical: true },
title,
body,
),
]),
actions,
),
)
}
export function NotificationPopups(monitor = 0) {
const list = Widget.Box({
vertical: true,
children: notifications.popups.map(Notification),
})
function onNotified(_, /** @type {number} */ id) {
const n = notifications.getNotification(id)
if (n)
list.children = [Notification(n), ...list.children]
}
function onDismissed(_, /** @type {number} */ id) {
list.children.find(n => n.attribute.id === id)?.destroy()
}
list.hook(notifications, onNotified, "notified")
.hook(notifications, onDismissed, "dismissed")
return Widget.Window({
monitor,
name: `notifications${monitor}`,
class_name: "notification-popups",
anchor: ["top", "right"],
child: Widget.Box({
css: "min-width: 2px; min-height: 2px;",
class_name: "notifications",
vertical: true,
child: list,
/** this is a simple one liner that could be used instead of
hooking into the 'notified' and 'dismissed' signals.
but its not very optimized becuase it will recreate
the whole list everytime a notification is added or dismissed */
// children: notifications.bind('popups')
// .as(popups => popups.map(Notification))
}),
})
}