feat: update ags

This commit is contained in:
2023-12-30 20:38:47 +01:00
parent d2f9104fe4
commit 32d78e57a3
213 changed files with 8155 additions and 9843 deletions

View File

@@ -0,0 +1,108 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
import options from "../../options.js";
import PanelButton from "../PanelButton.js";
const Indicator = () =>
Widget.Stack({
items: [
["false", Widget.Icon({ binds: [["icon", Battery, "icon-name"]] })],
["true", FontIcon(icons.battery.charging)],
],
binds: [["visible", options.battery.bar.show_icon]],
connections: [
[
Battery,
(stack) => {
stack.shown = `${Battery.charging || Battery.charged}`;
},
],
],
});
const PercentLabel = () =>
Widget.Revealer({
transition: "slide_right",
binds: [["reveal-child", options.battery.show_percentage]],
child: Widget.Label({
binds: [["label", Battery, "percent", (p) => `${p}%`]],
}),
});
const LevelBar = () =>
Widget.LevelBar({
connections: [
[
options.battery.bar.full,
(self) => {
const full = options.battery.bar.full.value;
self.vpack = full ? "fill" : "center";
self.hpack = full ? "fill" : "center";
},
],
],
binds: [["value", Battery, "percent", (p) => p / 100]],
});
const WholeButton = () =>
Widget.Overlay({
class_name: "whole-button",
child: LevelBar(),
pass_through: true,
overlays: [
Widget.Box({
hpack: "center",
children: [
FontIcon({
icon: icons.battery.charging,
binds: [["visible", Battery, "charging"]],
}),
Widget.Box({
hpack: "center",
vpack: "center",
child: PercentLabel(),
}),
],
}),
],
});
export default () =>
PanelButton({
class_name: "battery-bar",
on_clicked: () => {
const v = options.battery.show_percentage.value;
options.battery.show_percentage.value = !v;
},
content: Widget.Box({
connections: [
[
Battery,
(w) => {
w.toggleClassName("charging", Battery.charging || Battery.charged);
w.toggleClassName(
"medium",
Battery.percent < options.battery.medium.value,
);
w.toggleClassName(
"low",
Battery.percent < options.battery.low.value,
);
w.toggleClassName("half", Battery.percent < 48);
},
],
],
binds: [
["visible", Battery, "available"],
[
"children",
options.battery.bar.full,
"value",
(full) =>
full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()],
],
],
}),
});

View File

@@ -0,0 +1,27 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Colors from "../../services/colorpicker.js";
import PanelButton from "../PanelButton.js";
import Gdk from "gi://Gdk";
export default () =>
PanelButton({
class_name: "color-picker",
content: Widget.Icon("color-select-symbolic"),
binds: [["tooltip-text", Colors, "colors", (v) => `${v.length} colors`]],
on_clicked: () => Colors.pick(),
on_secondary_click: (btn) => {
if (Colors.colors.length === 0) return;
Widget.Menu({
class_name: "colorpicker",
children: Colors.colors.map((color) =>
Widget.MenuItem({
child: Widget.Label(color),
css: `background-color: ${color}`,
on_activate: () => Colors.wlCopy(color),
}),
),
}).popup_at_widget(btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null);
},
});

View File

@@ -0,0 +1,11 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Clock from "../../misc/Clock.js";
import PanelButton from "../PanelButton.js";
export default ({ format = "%H:%M - %A %e." } = {}) =>
PanelButton({
class_name: "dashboard panel-button",
on_clicked: () => App.toggleWindow("dashboard"),
window: "dashboard",
content: Clock({ format }),
});

View File

@@ -0,0 +1,55 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import PanelButton from "../PanelButton.js";
import options from "../../options.js";
import { substitute } from "../../utils.js";
export const ClientLabel = () =>
Widget.Label({
binds: [
[
"label",
Hyprland.active.client,
"class",
(c) => {
const { titles } = options.substitutions;
return substitute(titles, c);
},
],
],
});
export const ClientIcon = () =>
Widget.Icon({
connections: [
[
Hyprland.active.client,
(self) => {
const { icons } = options.substitutions;
const { client } = Hyprland.active;
const classIcon = substitute(icons, client.class) + "-symbolic";
const titleIcon = substitute(icons, client.class) + "-symbolic";
const hasTitleIcon = Utils.lookUpIcon(titleIcon);
const hasClassIcon = Utils.lookUpIcon(classIcon);
if (hasClassIcon) self.icon = classIcon;
if (hasTitleIcon) self.icon = titleIcon;
self.visible = !!(hasTitleIcon || hasClassIcon);
},
],
],
});
export default () =>
PanelButton({
class_name: "focused-client",
content: Widget.Box({
children: [ClientIcon(), ClientLabel()],
binds: [["tooltip-text", Hyprland.active, "client", (c) => c.title]],
}),
});

View File

@@ -0,0 +1,84 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import HoverRevealer from "../../misc/HoverRevealer.js";
import * as mpris from "../../misc/mpris.js";
import options from "../../options.js";
export const getPlayer = (name = options.mpris.preferred.value) =>
Mpris.getPlayer(name) || Mpris.players[0] || null;
/**
* @param {Object} o
* @param {import('types/service/mpris').MprisPlayer} o.player
* @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction
*/
const Indicator = ({ player, direction = "right" }) =>
HoverRevealer({
class_name: `media panel-button ${player.name}`,
direction,
on_primary_click: () => player.playPause(),
on_scroll_up: () => player.next(),
on_scroll_down: () => player.previous(),
on_secondary_click: () => player.playPause(),
indicator: mpris.PlayerIcon(player),
child: Widget.Label({
vexpand: true,
truncate: "end",
max_width_chars: 40,
connections: [
[
player,
(label) => {
label.label = `${player.track_artists.join(", ")} - ${
player.track_title
}`;
},
],
],
}),
connections: [
[
player,
(revealer) => {
if (revealer._current === player.track_title) return;
revealer._current = player.track_title;
revealer.reveal_child = true;
Utils.timeout(3000, () => {
revealer.reveal_child = false;
});
},
],
],
});
/**
* @param {Object} o
* @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction
*/
export default ({ direction = "right" } = {}) => {
let current = null;
const update = (box) => {
const player = getPlayer();
box.visible = !!player;
if (!player) {
current = null;
return;
}
if (current === player) return;
current = player;
box.children = [Indicator({ player, direction })];
};
return Widget.Box({
connections: [
[options.mpris.preferred, update],
[Mpris, update, "notify::players"],
],
});
};

View File

@@ -0,0 +1,63 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import HoverRevealer from "../../misc/HoverRevealer.js";
/**
* @param {Object} o
* @param {import('../../misc/HoverRevealer').HoverRevealProps['direction']=} o.direction
*/
export default ({ direction = "left" } = {}) =>
HoverRevealer({
class_name: "notifications panel-button",
eventboxConnections: [
["button-press-event", () => App.openWindow("dashboard")],
[
Notifications,
(box) =>
(box.visible =
Notifications.notifications.length > 0 || Notifications.dnd),
],
],
connections: [
[
Notifications,
(revealer) => {
const title = Notifications.notifications[0]?.summary;
if (revealer._title === title) return;
revealer._title = title;
revealer.reveal_child = true;
Utils.timeout(3000, () => {
revealer.reveal_child = false;
});
},
],
],
direction,
indicator: Widget.Icon({
binds: [
[
"icon",
Notifications,
"dnd",
(dnd) =>
dnd ? icons.notifications.silent : icons.notifications.noisy,
],
],
}),
child: Widget.Label({
truncate: "end",
max_width_chars: 40,
binds: [
[
"label",
Notifications,
"notifications",
(n) => n.reverse()[0]?.summary || "",
],
],
}),
});

View File

@@ -0,0 +1,24 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import PanelButton from "../PanelButton.js";
import FontIcon from "../../misc/FontIcon.js";
import { distroIcon } from "../../variables.js";
import options from "../../options.js";
export default () =>
PanelButton({
class_name: "overview",
window: "overview",
on_clicked: () => App.toggleWindow("overview"),
content: FontIcon({
binds: [
[
"icon",
options.bar.icon,
"value",
(v) => {
return v === "distro-icon" ? distroIcon : v;
},
],
],
}),
});

View File

@@ -0,0 +1,11 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import icons from "../../icons.js";
import PanelButton from "../PanelButton.js";
export default () =>
PanelButton({
class_name: "powermenu",
content: Widget.Icon(icons.powermenu.shutdown),
on_clicked: () => App.openWindow("powermenu"),
});

View File

@@ -0,0 +1,30 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PanelButton from "../PanelButton.js";
import Recorder from "../../services/screenrecord.js";
import icons from "../../icons.js";
export default () =>
PanelButton({
class_name: "recorder",
on_clicked: () => Recorder.stop(),
binds: [["visible", Recorder, "recording"]],
content: Widget.Box({
children: [
Widget.Icon(icons.recorder.recording),
Widget.Label({
binds: [
[
"label",
Recorder,
"timer",
(time) => {
const sec = time % 60;
const min = Math.floor(time / 60);
return `${min}:${sec < 10 ? "0" + sec : sec}`;
},
],
],
}),
],
}),
});

View File

@@ -0,0 +1,72 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import icons from "../../icons.js";
import options from "../../options.js";
/**
* @param {import('types/widgets/revealer').default} revealer
* @param {'left' | 'right' | 'up' | 'down'} direction
* @param {import('types/variable').Variable} items
*/
const Arrow = (revealer, direction, items) => {
let deg = 0;
const icon = Widget.Icon({
icon: icons.ui.arrow[direction],
});
const animate = () => {
const t = options.transition.value / 20;
const step = revealer.reveal_child ? 10 : -10;
for (let i = 0; i < 18; ++i) {
Utils.timeout(t * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
};
return Widget.Button({
class_name: "panel-button sub-menu",
connections: [
[
items,
(btn) => {
btn.tooltip_text = `${items.value} Items`;
},
],
],
on_clicked: () => {
animate();
revealer.reveal_child = !revealer.reveal_child;
},
child: icon,
});
};
/**
* @param {Object} o
* @param {import('types/widgets/box').default['children']} o.children
* @param {'left' | 'right' | 'up' | 'down'=} o.direction
* @param {import('types/variable').Variable} o.items
*/
export default ({ children, direction = "left", items = Variable(0) }) => {
const posStart = direction === "up" || direction === "left";
const posEnd = direction === "down" || direction === "right";
const revealer = Widget.Revealer({
transition: `slide_${direction}`,
child: Widget.Box({
children,
}),
});
return Widget.Box({
vertical: direction === "up" || direction === "down",
children: [
posStart && revealer,
Arrow(revealer, direction, items),
posEnd && revealer,
],
});
};

View File

@@ -0,0 +1,44 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js";
import PanelButton from "../PanelButton.js";
import Gdk from "gi://Gdk";
/** @param {import('types/service/systemtray').TrayItem} item */
const SysTrayItem = (item) =>
PanelButton({
class_name: "tray-item",
content: Widget.Icon({ icon: item.bind("icon") }),
tooltip_markup: item.bind("tooltip_markup"),
setup: (self) => {
const id = item.menu?.connect("popped-up", (menu) => {
self.toggleClassName("active");
menu.connect("notify::visible", (menu) => {
self.toggleClassName("active", menu.visible);
});
menu.disconnect(id);
});
if (id) self.connect("destroy", () => item.menu?.disconnect(id));
},
// @ts-expect-error popup_at_widget missing from types?
on_primary_click: (btn) =>
item.menu?.popup_at_widget(
btn,
Gdk.Gravity.SOUTH,
Gdk.Gravity.NORTH,
null,
),
// @ts-expect-error popup_at_widget missing from types?
on_secondary_click: (btn) =>
item.menu?.popup_at_widget(
btn,
Gdk.Gravity.SOUTH,
Gdk.Gravity.NORTH,
null,
),
});
export default () =>
Widget.Box().bind("children", SystemTray, "items", (i) => i.map(SysTrayItem));

View File

@@ -0,0 +1,57 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PanelButton from "../PanelButton.js";
import * as variables from "../../variables.js";
import icons from "../../icons.js";
/** @param {'cpu' | 'ram'} type */
const System = (type) => {
const icon = Widget.Icon({
class_name: "icon",
icon: icons.system[type],
});
const progress = Widget.Box({
class_name: "progress",
child: Widget.CircularProgress({
binds: [["value", variables[type]]],
}),
});
const revealer = Widget.Revealer({
transition: "slide_right",
child: Widget.Label({
binds: [
[
"label",
variables[type],
"value",
(v) => {
return ` ${type}: ${Math.round(v * 100)}%`;
},
],
],
}),
});
return PanelButton({
class_name: `system ${type}`,
on_clicked: () => (revealer.reveal_child = !revealer.reveal_child),
content: Widget.EventBox({
on_hover: () => (revealer.reveal_child = true),
on_hover_lost: () => (revealer.reveal_child = false),
child: Widget.Box({
children: [
icon,
Widget.Box({
class_name: "revealer",
child: revealer,
}),
progress,
],
}),
}),
});
};
export const CPU = () => System("cpu");
export const RAM = () => System("ram");

View File

@@ -0,0 +1,135 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js";
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Network from "resource:///com/github/Aylur/ags/service/network.js";
import HoverRevealer from "../../misc/HoverRevealer.js";
import PanelButton from "../PanelButton.js";
import Asusctl from "../../services/asusctl.js";
import Indicator from "../../services/onScreenIndicator.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
const ProfileIndicator = () =>
Widget.Icon()
.bind("visible", Asusctl, "profile", (p) => p !== "Balanced")
.bind("icon", Asusctl, "profile", (i) => icons.asusctl.profile[i]);
const ModeIndicator = () =>
FontIcon()
.bind("visible", Asusctl, "mode", (m) => m !== "Hybrid")
.bind("icon", Asusctl, "mode", (i) => icons.asusctl.mode[i]);
const MicrophoneIndicator = () =>
Widget.Icon().hook(
Audio,
(icon) => {
if (!Audio.microphone) return;
const { muted, low, medium, high } = icons.audio.mic;
if (Audio.microphone.is_muted) return (icon.icon = muted);
/** @type {Array<[number, string]>} */
const cons = [
[67, high],
[34, medium],
[1, low],
[0, muted],
];
icon.icon =
cons.find(([n]) => n <= Audio.microphone.volume * 100)?.[1] || "";
icon.visible = Audio.recorders.length > 0 || Audio.microphone.is_muted;
},
"speaker-changed",
);
const DNDIndicator = () =>
Widget.Icon({
visible: Notifications.bind("dnd"),
icon: icons.notifications.silent,
});
const BluetoothDevicesIndicator = () =>
Widget.Box().hook(
Bluetooth,
(box) => {
box.children = Bluetooth.connectedDevices.map(({ iconName, name }) =>
HoverRevealer({
indicator: Widget.Icon(iconName + "-symbolic"),
child: Widget.Label(name),
}),
);
box.visible = Bluetooth.connectedDevices.length > 0;
},
"notify::connected-devices",
);
const BluetoothIndicator = () =>
Widget.Icon({
class_name: "bluetooth",
icon: icons.bluetooth.enabled,
visible: Bluetooth.bind("enabled"),
});
const NetworkIndicator = () =>
Widget.Icon().hook(Network, (self) => {
const icon = Network[Network.primary || "wifi"]?.iconName;
self.icon = icon || "";
self.visible = !!icon;
});
const AudioIndicator = () =>
Widget.Icon().hook(
Audio,
(self) => {
if (!Audio.speaker) return;
const { muted, low, medium, high, overamplified } = icons.audio.volume;
if (Audio.speaker.is_muted) return (self.icon = muted);
/** @type {Array<[number, string]>} */
const cons = [
[101, overamplified],
[67, high],
[34, medium],
[1, low],
[0, muted],
];
self.icon =
cons.find(([n]) => n <= Audio.speaker.volume * 100)?.[1] || "";
},
"speaker-changed",
);
export default () =>
PanelButton({
class_name: "quicksettings panel-button",
on_clicked: () => App.toggleWindow("quicksettings"),
setup: (self) =>
self.hook(App, (_, win, visible) => {
self.toggleClassName("active", win === "quicksettings" && visible);
}),
on_scroll_up: () => {
Audio.speaker.volume += 0.02;
Indicator.speaker();
},
on_scroll_down: () => {
Audio.speaker.volume -= 0.02;
Indicator.speaker();
},
content: Widget.Box({
children: [
Asusctl?.available && ProfileIndicator(),
Asusctl?.available && ModeIndicator(),
DNDIndicator(),
BluetoothDevicesIndicator(),
BluetoothIndicator(),
NetworkIndicator(),
AudioIndicator(),
MicrophoneIndicator(),
],
}),
});

View File

@@ -0,0 +1,34 @@
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PanelButton from "../PanelButton.js";
import { launchApp } from "../../utils.js";
import icons from "../../icons.js";
const focus = ({ address }) =>
Hyprland.sendMessage(`dispatch focuswindow address:${address}`);
/** @param {import('types/widgets/box').default} box */
const setChildren = (box) =>
(box.children = Hyprland.clients.map((client) => {
if (Hyprland.active.workspace.id !== client.workspace.id) return;
for (const app of Applications.list) {
if (client.class && app.match(client.class)) {
return PanelButton({
content: Widget.Icon(app.icon_name || icons.fallback.executable),
tooltip_text: app.name,
on_primary_click: () => focus(client),
on_middle_click: () => launchApp(app),
});
}
}
}));
export default () =>
Widget.Box({
connections: [
[Hyprland, setChildren, "notify::clients"],
[Hyprland, setChildren, "notify::active"],
],
});

View File

@@ -0,0 +1,64 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import options from "../../options.js";
import { range } from "../../utils.js";
/** @param {any} arg */
const dispatch = (arg) => Utils.execAsync(`hyprctl dispatch workspace ${arg}`);
const Workspaces = () => {
const ws = options.workspaces.value;
return Widget.Box({
children: range(ws || 20).map((i) =>
Widget.Button({
setup: (btn) => (btn.id = i),
on_clicked: () => dispatch(i),
child: Widget.Label({
label: `${i}`,
class_name: "indicator",
vpack: "center",
}),
connections: [
[
Hyprland,
(btn) => {
btn.toggleClassName("active", Hyprland.active.workspace.id === i);
btn.toggleClassName(
"occupied",
Hyprland.getWorkspace(i)?.windows > 0,
);
},
],
],
}),
),
connections: ws
? []
: [
[
Hyprland.active.workspace,
(box) =>
box.children.map((btn) => {
btn.visible = Hyprland.workspaces.some(
(ws) => ws.id === btn.id,
);
}),
],
],
});
};
export default () =>
Widget.EventBox({
class_name: "workspaces panel-button",
child: Widget.Box({
// its nested like this to keep it consistent with other PanelButton widgets
child: Widget.EventBox({
on_scroll_up: () => dispatch("m+1"),
on_scroll_down: () => dispatch("m-1"),
class_name: "eventbox",
binds: [["child", options.workspaces, "value", Workspaces]],
}),
}),
});