added ags notification service and ags media widget

This commit is contained in:
wt
2024-09-02 11:24:18 +07:00
parent 6fc80b3e44
commit 13d29bcb2c
13 changed files with 478 additions and 3 deletions
+153
View File
@@ -0,0 +1,153 @@
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)),
})
}
+18
View File
@@ -0,0 +1,18 @@
# Media Widget
setup
```bash
mkdir -p ~/.config/ags
git clone https://github.com/Aylur/ags.git /tmp/ags
cp -r /tmp/ags/example/media-widget/* ~/.config/ags
# optionally setup types
ags --init -c ~/.config/ags/config.js
```
running
```bash
ags -c ~/.config/ags/config.js &
```
+13
View File
@@ -0,0 +1,13 @@
import { Media } from "./Media.js"
const win = Widget.Window({
name: "mpris",
css: "border-radius: 16px;",
anchor: ["top", "right"],
child: Media(),
})
App.config({
style: "./style.css",
windows: [win],
})
+49
View File
@@ -0,0 +1,49 @@
.player {
padding: 10px;
min-width: 350px;
}
.player .img {
min-width: 100px;
min-height: 100px;
background-size: cover;
background-position: center;
border-radius: 13px;
margin-right: 1em;
}
.player .title {
font-size: 1.2em;
}
.player .artist {
font-size: 1.1em;
color: @insensitive_fg_color;
}
.player scale.position {
padding: 0;
margin-bottom: .3em;
}
.player scale.position trough {
min-height: 8px;
}
.player scale.position highlight {
background-color: @theme_fg_color;
}
.player scale.position slider {
all: unset;
}
.player button {
min-height: 1em;
min-width: 1em;
padding: .3em;
}
.player button.play-pause {
margin: 0 .3em;
}
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
if [ ! -f ~/.ags_media ]; then
ags --bus-name media -c ~/dotfiles/ags/media/config.js &
echo $! > ~/.ags_media
else
ags --bus-name media -c ~/dotfiles/ags/media/config.js --quit
rm ~/.ags_media
fi
+18
View File
@@ -0,0 +1,18 @@
# Notification Popups
setup
```bash
mkdir -p ~/.config/ags
git clone https://github.com/Aylur/ags.git /tmp/ags
cp -r /tmp/ags/example/notification-popups/* ~/.config/ags
# optionally setup types
ags --init -c ~/.config/ags/config.js
```
running
```bash
ags -c ~/.config/ags/config.js &
```
+18
View File
@@ -0,0 +1,18 @@
import { NotificationPopups } from "./notificationPopups.js"
Utils.timeout(100, () => Utils.notify({
summary: "Notification Popup Example",
iconName: "info-symbolic",
body: "Lorem ipsum dolor sit amet, qui minim labore adipisicing "
+ "minim sint cillum sint consectetur cupidatat.",
actions: {
"Cool": () => print("pressed Cool"),
},
}))
App.config({
style: App.configDir + "/style.css",
windows: [
NotificationPopups(),
],
})
@@ -0,0 +1,130 @@
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) {
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: n.body,
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))
}),
})
}
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
if [ ! -f ~/.ags_notification_service ]; then
ags --bus-name notification_service -c ~/dotfiles/ags/notification-popups/config.js &
echo $! > ~/.ags_notification_service
else
ags --bus-name notification_service -c ~/dotfiles/ags/notification-popups/config.js --quit
rm ~/.ags_notification_service
fi
+57
View File
@@ -0,0 +1,57 @@
window.notification-popups box.notifications {
padding: .5em;
}
.icon {
min-width: 68px;
min-height: 68px;
margin-right: 1em;
}
.icon image {
font-size: 58px;
/* to center the icon */
margin: 5px;
color: @theme_fg_color;
}
.icon box {
min-width: 68px;
min-height: 68px;
border-radius: 7px;
}
.notification {
min-width: 350px;
border-radius: 11px;
padding: 1em;
margin: .5em;
border: 1px solid @wm_borders_edge;
background-color: @theme_bg_color;
}
.notification.critical {
border: 1px solid lightcoral;
}
.title {
color: @theme_fg_color;
font-size: 1.4em;
}
.body {
color: @theme_unfocused_fg_color;
}
.actions .action-button {
margin: 0 .4em;
margin-top: .8em;
}
.actions .action-button:first-child {
margin-left: 0;
}
.actions .action-button:last-child {
margin-right: 0;
}
+1 -1
View File
@@ -18,7 +18,7 @@ bind = $mainMod SHIFT, O, exec, $onscreenkeyboard
bind = $mainMod, S, exec, spotify
bind = $mainMod, Y, exec, $calculator
bind = $mainMod, E, exec, $emojipicker
bind = $mainMod, M, exec, ~/dotfiles/eww/toggle_music
bind = $mainMod, M, exec, ~/dotfiles/ags/media/toggle_media
#---------------------------------------------------
#-------------- windows --------------------------
+2 -1
View File
@@ -23,7 +23,8 @@ exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESK
exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
#------------------------------------------------------------------------------------------------
exec-once = mako
exec-once = ~/dotfiles/ags/notification-popups/start_notification_service
# exec-once = mako
exec-once = nm-applet --indicator
exec-once = greenclip daemon
exec-once = uxplay
+1 -1
View File
@@ -229,7 +229,7 @@
"playing": "",
"paused": ""
},
"on-click-right": "~/dotfiles/eww/toggle_music",
"on-click-right": "~/dotfiles/ags/media/toggle_media",
// "ignored-players": ["firefox"]
}
}