feat: update system

This commit is contained in:
2024-02-07 14:25:34 +01:00
parent 3bfeb8e6fc
commit 09afd0bef6
67 changed files with 933 additions and 1347 deletions

View File

@@ -1,22 +1,2 @@
import { readFile } from "resource:///com/github/Aylur/ags/utils.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import { timeout } from "resource:///com/github/Aylur/ags/utils.js";
const pkgjson = JSON.parse(readFile(App.configDir + "/package.json"));
timeout(1000, () => JSON.stringify(App));
const v = {
ags: `v${pkg.version}`,
expected: `v${pkgjson.version}`,
};
function mismatch() {
print(`my config expects ${v.expected}, but your ags is ${v.ags}`);
App.connect("config-parsed", (app) => app.Quit());
return {};
}
/*export default v.ags === v.expected
? (await import("./js/main.js")).default
: mismatch();*/
export default (await import("./js/main.js")).default;
import { default as main } from "./js/main.js";
export default main;

View File

@@ -1,6 +1,3 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import PopupWindow from "../misc/PopupWindow.js";
import icons from "../icons.js";

View File

@@ -1,7 +1,4 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import options from "../options.js";
import { lookUpIcon } from "resource:///com/github/Aylur/ags/utils.js";
/** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */
export default (app) => {
@@ -23,7 +20,7 @@ export default (app) => {
});
const icon = Widget.Icon({
icon: lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "",
icon: Utils.lookUpIcon(app.icon_name || "") ? app.icon_name || "" : "",
size: options.applauncher.icon_size.bind("value"),
});

View File

@@ -1,10 +1,9 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
import PopupWindow from "../misc/PopupWindow.js";
import AppItem from "./AppItem.js";
import icons from "../icons.js";
import { launchApp } from "../utils.js";
import options from "../options.js";
const WINDOW_NAME = "applauncher";
@@ -30,6 +29,7 @@ const Applauncher = () => {
let items = mkItems();
const list = Widget.Box({
class_name: "app-list",
vertical: true,
children: items,
});
@@ -86,4 +86,5 @@ export default () =>
name: WINDOW_NAME,
transition: "slide_down",
child: Applauncher(),
anchor: options.applauncher.anchor.bind("value"),
});

View File

@@ -1,45 +1,34 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
/**
* @typedef {Object} PanelButtonProps
* @property {any} content
* @property {import('types/widgets/button').ButtonProps['child']} content
* @property {string=} window
*/
/**
* @param {import('types/widgets/button').ButtonProps & PanelButtonProps} o
*/
export default ({
class_name,
content,
window = "",
connections = [],
...rest
}) => {
let open = false;
const connection = [
App,
(self, win, visible) => {
if (win !== window) return;
if (open && !visible) {
open = false;
self.toggleClassName("active", false);
}
if (visible) {
open = true;
self.toggleClassName("active");
}
},
];
return Widget.Button({
export default ({ class_name, content, window = "", setup, ...rest }) =>
Widget.Button({
class_name: `panel-button ${class_name}`,
child: Widget.Box({ children: [content] }),
connections: connections.concat([connection]),
setup: (self) => {
let open = false;
self.hook(App, (_, win, visible) => {
if (win !== window) return;
if (open && !visible) {
open = false;
self.toggleClassName("active", false);
}
if (visible) {
open = true;
self.toggleClassName("active");
}
});
if (setup) setup(self);
},
...rest,
});
};

View File

@@ -1,6 +1,4 @@
import SystemTray from "resource:///com/github/Aylur/ags/service/systemtray.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
@@ -28,25 +26,29 @@ SystemTray.connect("changed", () => {
});
/**
* @template T
* @template {import('types/service').default} T
* @param {T=} service
* @param {(self: T) => boolean=} condition
* @param {(service: T) => boolean=} condition
*/
const SeparatorDot = (service, condition) => {
const visibility = (self) => {
if (!options.bar.separators.value) return (self.visible = false);
self.visible =
condition && service ? condition(service) : options.bar.separators.value;
};
const conn = service ? [[service, visibility]] : [];
return Widget.Separator({
connections: [["draw", visibility], ...conn],
binds: [["visible", options.bar.separators]],
const SeparatorDot = (service, condition) =>
Widget.Separator({
vpack: "center",
setup: (self) => {
const visibility = () => {
if (!options.bar.separators.value) return (self.visible = false);
self.visible =
condition && service
? condition(service)
: options.bar.separators.value;
};
if (service && condition) self.hook(service, visibility);
self.on("draw", visibility);
self.bind("visible", options.bar.separators);
},
});
};
const Start = () =>
Widget.Box({
@@ -99,14 +101,9 @@ export default (monitor) =>
class_name: "transparent",
exclusivity: "exclusive",
monitor,
binds: [
[
"anchor",
options.bar.position,
"value",
(pos) => [pos, "left", "right"],
],
],
anchor: options.bar.position
.bind("value")
.transform((pos) => [pos, "left", "right"]),
child: Widget.CenterBox({
class_name: "panel",
start_widget: Start(),

View File

@@ -7,43 +7,35 @@ 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}`;
},
],
],
children: {
false: Widget.Icon({ icon: Battery.bind("icon_name") }),
true: FontIcon(icons.battery.charging),
},
visible: options.battery.bar.show_icon.bind("value"),
setup: (self) =>
self.hook(Battery, () => {
self.shown = `${Battery.charging || Battery.charged}`;
}),
});
const PercentLabel = () =>
Widget.Revealer({
transition: "slide_right",
binds: [["reveal-child", options.battery.show_percentage]],
reveal_child: options.battery.show_percentage.bind("value"),
child: Widget.Label({
binds: [["label", Battery, "percent", (p) => `${p}%`]],
label: Battery.bind("percent").transform((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]],
value: Battery.bind("percent").transform((p) => p / 100),
setup: (self) =>
self.hook(options.battery.bar.full, () => {
const full = options.battery.bar.full.value;
self.vpack = full ? "fill" : "center";
self.hpack = full ? "fill" : "center";
}),
});
const WholeButton = () =>
@@ -57,7 +49,7 @@ const WholeButton = () =>
children: [
FontIcon({
icon: icons.battery.charging,
binds: [["visible", Battery, "charging"]],
visible: Battery.bind("charging"),
}),
Widget.Box({
hpack: "center",
@@ -77,32 +69,21 @@ export default () =>
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()],
],
],
visible: Battery.bind("available"),
children: options.battery.bar.full
.bind("value")
.transform((full) =>
full ? [WholeButton()] : [Indicator(), PercentLabel(), LevelBar()],
),
setup: (self) =>
self.hook(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);
}),
}),
});

View File

@@ -7,9 +7,8 @@ export default () =>
PanelButton({
class_name: "color-picker",
content: Widget.Icon("color-select-symbolic"),
binds: [["tooltip-text", Colors, "colors", (v) => `${v.length} colors`]],
tooltip_text: Colors.bind("colors").transform((v) => `${v.length} colors`),
on_clicked: () => Colors.pick(),
on_secondary_click: (btn) => {
if (Colors.colors.length === 0) return;

View File

@@ -7,49 +7,38 @@ 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);
},
],
],
label: Hyprland.active.client.bind("class").transform((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;
setup: (self) =>
self.hook(Hyprland.active.client, () => {
const { icons } = options.substitutions;
const { client } = Hyprland.active;
const classIcon = substitute(icons, client.class) + "-symbolic";
const titleIcon = substitute(icons, client.class) + "-symbolic";
const classIcon = substitute(icons, client.class) + "-symbolic";
const titleIcon = substitute(icons, client.class) + "-symbolic";
const hasTitleIcon = Utils.lookUpIcon(titleIcon);
const hasClassIcon = Utils.lookUpIcon(classIcon);
const hasTitleIcon = Utils.lookUpIcon(titleIcon);
const hasClassIcon = Utils.lookUpIcon(classIcon);
if (hasClassIcon) self.icon = classIcon;
if (hasClassIcon) self.icon = classIcon;
if (hasTitleIcon) self.icon = titleIcon;
if (hasTitleIcon) self.icon = titleIcon;
self.visible = !!(hasTitleIcon || hasClassIcon);
},
],
],
self.visible = !!(hasTitleIcon || hasClassIcon);
}),
});
export default () =>
PanelButton({
class_name: "focused-client",
content: Widget.Box({
tooltip_text: Hyprland.active.bind("client").transform((c) => c.title),
children: [ClientIcon(), ClientLabel()],
binds: [["tooltip-text", Hyprland.active, "client", (c) => c.title]],
}),
});

View File

@@ -26,31 +26,24 @@ const Indicator = ({ player, direction = "right" }) =>
vexpand: true,
truncate: "end",
max_width_chars: 40,
connections: [
[
player,
(label) => {
label.label = `${player.track_artists.join(", ")} - ${
player.track_title
}`;
},
],
],
label: player
.bind("track_title")
.transform(
() => `${player.track_artists.join(", ")} - ${player.track_title}`,
),
}),
connections: [
[
player,
(revealer) => {
if (revealer._current === player.track_title) return;
setupRevealer: (self) => {
let current = "";
self.hook(player, () => {
if (current === player.track_title) return;
revealer._current = player.track_title;
revealer.reveal_child = true;
Utils.timeout(3000, () => {
revealer.reveal_child = false;
});
},
],
],
current = player.track_title;
self.reveal_child = true;
Utils.timeout(3000, () => {
self.reveal_child = false;
});
});
},
});
/**
@@ -75,10 +68,7 @@ export default ({ direction = "right" } = {}) => {
box.children = [Indicator({ player, direction })];
};
return Widget.Box({
connections: [
[options.mpris.preferred, update],
[Mpris, update, "notify::players"],
],
});
return Widget.Box()
.hook(options.mpris.preferred, update)
.hook(Mpris, update, "notify::players");
};

View File

@@ -12,52 +12,39 @@ import HoverRevealer from "../../misc/HoverRevealer.js";
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;
setupEventBox: (box) =>
box
.on("button-press-event", () => App.openWindow("dashboard"))
.hook(
Notifications,
() =>
(box.visible =
Notifications.notifications.length > 0 || Notifications.dnd),
),
revealer._title = title;
revealer.reveal_child = true;
Utils.timeout(3000, () => {
revealer.reveal_child = false;
});
},
],
],
setupRevealer: (self) =>
self.hook(Notifications, () => {
let title = "";
const summary = Notifications.notifications[0]?.summary;
if (title === summary) return;
title = summary;
self.reveal_child = true;
Utils.timeout(3000, () => {
self.reveal_child = false;
});
}),
direction,
indicator: Widget.Icon({
binds: [
[
"icon",
Notifications,
"dnd",
(dnd) =>
dnd ? icons.notifications.silent : icons.notifications.noisy,
],
],
icon: Notifications.bind("dnd").transform(
(dnd) => icons.notifications[dnd ? "silent" : "noisy"],
),
}),
child: Widget.Label({
truncate: "end",
max_width_chars: 40,
binds: [
[
"label",
Notifications,
"notifications",
(n) => n.reverse()[0]?.summary || "",
],
],
label: Notifications.bind("notifications").transform(
(n) => n.reverse()[0]?.summary || "",
),
}),
});

View File

@@ -10,15 +10,8 @@ export default () =>
window: "overview",
on_clicked: () => App.toggleWindow("overview"),
content: FontIcon({
binds: [
[
"icon",
options.bar.icon,
"value",
(v) => {
return v === "distro-icon" ? distroIcon : v;
},
],
],
label: options.bar.icon.bind("value").transform((v) => {
return v === "distro-icon" ? distroIcon : v;
}),
}),
});

View File

@@ -7,23 +7,16 @@ export default () =>
PanelButton({
class_name: "recorder",
on_clicked: () => Recorder.stop(),
binds: [["visible", Recorder, "recording"]],
visible: Recorder.bind("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}`;
},
],
],
label: Recorder.bind("timer").transform((time) => {
const sec = time % 60;
const min = Math.floor(time / 60);
return `${min}:${sec < 10 ? "0" + sec : sec}`;
}),
}),
],
}),

View File

@@ -7,7 +7,7 @@ import options from "../../options.js";
/**
* @param {import('types/widgets/revealer').default} revealer
* @param {'left' | 'right' | 'up' | 'down'} direction
* @param {import('types/variable').Variable} items
* @param {import('types/variable').Variable<number>} items
*/
const Arrow = (revealer, direction, items) => {
let deg = 0;
@@ -29,14 +29,7 @@ const Arrow = (revealer, direction, items) => {
return Widget.Button({
class_name: "panel-button sub-menu",
connections: [
[
items,
(btn) => {
btn.tooltip_text = `${items.value} Items`;
},
],
],
tooltip_text: items.bind().transform((v) => `${v} Items`),
on_clicked: () => {
animate();
revealer.reveal_child = !revealer.reveal_child;

View File

@@ -13,23 +13,16 @@ const System = (type) => {
const progress = Widget.Box({
class_name: "progress",
child: Widget.CircularProgress({
binds: [["value", variables[type]]],
value: variables[type].bind(),
}),
});
const revealer = Widget.Revealer({
transition: "slide_right",
child: Widget.Label({
binds: [
[
"label",
variables[type],
"value",
(v) => {
return ` ${type}: ${Math.round(v * 100)}%`;
},
],
],
label: variables[type].bind("value").transform((v) => {
return ` ${type}: ${Math.round(v * 100)}%`;
}),
}),
});

View File

@@ -28,7 +28,6 @@ const MicrophoneIndicator = () =>
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 = [
@@ -42,7 +41,7 @@ const MicrophoneIndicator = () =>
icon.visible = Audio.recorders.length > 0 || Audio.microphone.is_muted;
},
"speaker-changed",
"microphone-changed",
);
const DNDIndicator = () =>

View File

@@ -26,9 +26,6 @@ const setChildren = (box) =>
}));
export default () =>
Widget.Box({
connections: [
[Hyprland, setChildren, "notify::clients"],
[Hyprland, setChildren, "notify::active"],
],
});
Widget.Box()
.hook(Hyprland, setChildren, "notify::clients")
.hook(Hyprland, setChildren, "notify::active");

View File

@@ -12,40 +12,34 @@ const Workspaces = () => {
return Widget.Box({
children: range(ws || 20).map((i) =>
Widget.Button({
setup: (btn) => (btn.id = i),
attribute: 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,
);
},
],
],
setup: (self) =>
self.hook(Hyprland, () => {
self.toggleClassName("active", Hyprland.active.workspace.id === i);
self.toggleClassName(
"occupied",
(Hyprland.getWorkspace(i)?.windows || 0) > 0,
);
}),
}),
),
connections: ws
? []
: [
[
Hyprland.active.workspace,
(box) =>
box.children.map((btn) => {
btn.visible = Hyprland.workspaces.some(
(ws) => ws.id === btn.id,
);
}),
],
],
setup: (box) => {
if (ws === 0) {
box.hook(Hyprland.active.workspace, () =>
box.children.map((btn) => {
btn.visible = Hyprland.workspaces.some(
(ws) => ws.id === btn.attribute,
);
}),
);
}
},
});
};
@@ -58,7 +52,7 @@ export default () =>
on_scroll_up: () => dispatch("m+1"),
on_scroll_down: () => dispatch("m-1"),
class_name: "eventbox",
binds: [["child", options.workspaces, "value", Workspaces]],
child: options.workspaces.bind("value").transform(Workspaces),
}),
}),
});

View File

@@ -7,19 +7,15 @@ import options from "../options.js";
export default () =>
PopupWindow({
name: "dashboard",
connections: [
[
options.bar.position,
(self) => {
self.anchor = [options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
setup: (self) =>
self.hook(options.bar.position, () => {
self.anchor = [options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
},
],
],
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
}),
child: Widget.Box({
children: [
NotificationColumn(),

View File

@@ -13,23 +13,16 @@ const SysProgress = (type, title, unit) =>
Widget.Box({
class_name: `circular-progress-box ${type}`,
hexpand: true,
binds: [
[
"tooltipText",
vars[type],
"value",
(v) => `${title}: ${Math.floor(v * 100)}${unit}`,
],
],
tooltip_text: vars[type]
.bind("value")
.transform((v) => `${title}: ${Math.floor(v * 100)}${unit}`),
child: Widget.CircularProgress({
hexpand: true,
class_name: `circular-progress ${type}`,
child: Widget.Icon(icons.system[type]),
start_at: 0.75,
binds: [
["value", vars[type]],
["rounded", options.radii, "value", (v) => v > 0],
],
value: vars[type].bind(),
rounded: options.radii.bind("value").transform((v) => v > 0),
}),
});
@@ -45,7 +38,7 @@ export default () =>
Clock({ format: "%H:%M" }),
Widget.Label({
class_name: "uptime",
binds: [["label", vars.uptime, "value", (t) => `uptime: ${t}`]],
label: vars.uptime.bind("value").transform((t) => `uptime: ${t}`),
}),
],
}),

View File

@@ -11,19 +11,16 @@ const ClearButton = () =>
for (let i = 0; i < list.length; i++)
timeout(50 * i, () => list[i]?.close());
},
binds: [["sensitive", Notifications, "notifications", (n) => n.length > 0]],
sensitive: Notifications.bind("notifications").transform(
(n) => n.length > 0,
),
child: Widget.Box({
children: [
Widget.Label("Clear "),
Widget.Icon({
binds: [
[
"icon",
Notifications,
"notifications",
(n) => (n.length > 0 ? icons.trash.full : icons.trash.empty),
],
],
icon: Notifications.bind("notifications").transform(
(n) => icons.trash[n.length > 0 ? "full" : "empty"],
),
}),
],
}),
@@ -42,18 +39,10 @@ const NotificationList = () =>
Widget.Box({
vertical: true,
vexpand: true,
connections: [
[
Notifications,
(box) => {
box.children = Notifications.notifications
.reverse()
.map(Notification);
box.visible = Notifications.notifications.length > 0;
},
],
],
children: Notifications.bind("notifications").transform((n) =>
n.reverse().map(Notification),
),
visible: Notifications.bind("notifications").transform((n) => n.length > 0),
});
const Placeholder = () =>
@@ -64,11 +53,13 @@ const Placeholder = () =>
hpack: "center",
vexpand: true,
hexpand: true,
visible: Notifications.bind("notifications").transform(
(n) => n.length === 0,
),
children: [
Widget.Icon(icons.notifications.silent),
Widget.Label("Your inbox is empty"),
],
binds: [["visible", Notifications, "notifications", (n) => n.length === 0]],
});
export default () =>

View File

@@ -8,28 +8,26 @@ const DesktopClock = () =>
class_name: "clock-box-shadow",
child: Widget.CenterBox({
class_name: "clock-box",
children: [
Clock({
class_name: "clock",
hpack: "center",
format: "%H",
}),
Widget.Box({
class_name: "separator-box",
vertical: true,
hexpand: true,
hpack: "center",
children: [
Widget.Separator({ vpack: "center", vexpand: true }),
Widget.Separator({ vpack: "center", vexpand: true }),
],
}),
Clock({
class_name: "clock",
hpack: "center",
format: "%M",
}),
],
start_widget: Clock({
class_name: "clock",
hpack: "center",
format: "%H",
}),
center_widget: Widget.Box({
class_name: "separator-box",
vertical: true,
hexpand: true,
hpack: "center",
children: [
Widget.Separator({ vpack: "center", vexpand: true }),
Widget.Separator({ vpack: "center", vexpand: true }),
],
}),
end_widget: Clock({
class_name: "clock",
hpack: "center",
format: "%M",
}),
}),
});
@@ -40,21 +38,17 @@ const Desktop = () =>
vertical: true,
vexpand: true,
hexpand: true,
binds: [["visible", options.desktop.clock.enable]],
connections: [
[
options.desktop.clock.position,
(box) => {
const [hpack = "center", vpack = "center", offset = 64] =
options.desktop.clock.position.value.split(" ") || [];
visible: options.desktop.clock.enable.bind("value"),
setup: (self) =>
self.hook(options.desktop.clock.position, () => {
const [hpack = "center", vpack = "center", offset = 64] =
options.desktop.clock.position.value.split(" ") || [];
// @ts-expect-error
box.hpack = hpack;
box.vpack = vpack;
box.setCss(`margin: ${Number(offset)}px;`);
},
],
],
// @ts-expect-error
self.hpack = hpack;
self.vpack = vpack;
self.setCss(`margin: ${Number(offset)}px;`);
}),
children: [
DesktopClock(),
Clock({ format: "%B %e. %A", class_name: "date" }),
@@ -66,6 +60,7 @@ const Desktop = () =>
export default (monitor) =>
Widget.Window({
monitor,
keymode: "on-demand",
name: `desktop${monitor}`,
layer: "background",
class_name: "desktop",

View File

@@ -22,113 +22,95 @@ const AppButton = ({ icon, pinned = false, ...rest }) => {
),
});
const button = Widget.Button({
return Widget.Button({
...rest,
attribute: indicators,
child: Widget.Box({
class_name: "box",
child: Widget.Overlay({
child: Widget.Icon({
icon,
binds: [["size", options.desktop.dock.icon_size]],
size: options.desktop.dock.icon_size.bind("value"),
}),
pass_through: true,
overlays: pinned ? [indicators] : [],
}),
}),
});
return Object.assign(button, { indicators });
};
const Taskbar = () =>
Widget.Box({
binds: [
[
"children",
Hyprland,
"clients",
(c) =>
c.map((client) => {
for (const appName of options.desktop.dock.pinned_apps.value) {
if (client.class.toLowerCase().includes(appName.toLowerCase()))
return null;
}
for (const app of Applications.list) {
if (
(client.title && app.match(client.title)) ||
(client.class && app.match(client.class))
) {
return AppButton({
icon: app.icon_name || "",
tooltip_text: app.name,
on_primary_click: () => focus(client),
on_middle_click: () => launchApp(app),
});
}
}
}),
],
],
children: Hyprland.bind("clients").transform((c) =>
c.map((client) => {
for (const appName of options.desktop.dock.pinned_apps.value) {
if (client.class.toLowerCase().includes(appName.toLowerCase()))
return null;
}
for (const app of Applications.list) {
if (
(client.title && app.match(client.title)) ||
(client.class && app.match(client.class))
) {
return AppButton({
icon: app.icon_name || "",
tooltip_text: app.name,
on_primary_click: () => focus(client),
on_middle_click: () => launchApp(app),
});
}
}
}),
),
});
const PinnedApps = () =>
Widget.Box({
class_name: "pins",
homogeneous: true,
binds: [
[
"children",
options.desktop.dock.pinned_apps,
"value",
(v) =>
v
.map((term) => ({ app: Applications.query(term)?.[0], term }))
.filter(({ app }) => app)
.map(({ app, term = true }) =>
AppButton({
pinned: true,
icon: app.icon_name || "",
on_primary_click: () => {
for (const client of Hyprland.clients) {
if (client.class.toLowerCase().includes(term))
return focus(client);
}
children: options.desktop.dock.pinned_apps.bind("value").transform((v) =>
v
.map((term) => ({ app: Applications.query(term)?.[0], term }))
.filter(({ app }) => app)
.map(({ app, term }) =>
AppButton({
pinned: true,
icon: app.icon_name || "",
on_primary_click: () => {
for (const client of Hyprland.clients) {
if (client.class.toLowerCase().includes(term))
return focus(client);
}
launchApp(app);
},
on_middle_click: () => launchApp(app),
tooltip_text: app.name,
connections: [
[
Hyprland,
(button) => {
const running = Hyprland.clients.filter((client) =>
client.class.toLowerCase().includes(term),
);
launchApp(app);
},
on_middle_click: () => launchApp(app),
tooltip_text: app.name,
setup: (button) =>
button.hook(Hyprland, () => {
const running = Hyprland.clients.filter((client) =>
client.class.toLowerCase().includes(term),
);
const focused = running.find(
(client) =>
client.address === Hyprland.active.client.address,
);
const focused = running.find(
(client) => client.address === Hyprland.active.client.address,
);
const index = running.findIndex((c) => c === focused);
const index = running.findIndex((c) => c === focused);
for (let i = 0; i < 5; ++i) {
const indicator = button.indicators.children[i];
indicator.visible = i < running.length;
indicator.toggleClassName("focused", i === index);
}
for (let i = 0; i < 5; ++i) {
const indicator = button.attribute.children[i];
indicator.visible = i < running.length;
indicator.toggleClassName("focused", i === index);
}
button.set_tooltip_text(
running.length === 1 ? running[0].title : app.name,
);
},
],
],
button.set_tooltip_text(
running.length === 1 ? running[0].title : app.name,
);
}),
),
],
],
}),
),
),
});
export default () => {
@@ -144,9 +126,14 @@ export default () => {
vpack: "center",
hpack: "center",
orientation: 1,
connections: [
[Hyprland, (box) => (box.visible = taskbar.children.length > 0)],
],
setup: (self) =>
self.hook(
taskbar,
() => {
self.visible = taskbar.children.length > 0;
},
"notify::children",
),
});
return Widget.Box({
class_name: "dock",

View File

@@ -35,20 +35,10 @@ export default (monitor) => {
}),
],
}),
connections: [
[
"enter-notify-event",
() => {
revealer.reveal_child = true;
},
],
[
"leave-notify-event",
() => {
revealer.reveal_child = false;
},
],
],
binds: [["visible", options.bar.position, "value", (v) => v !== "bottom"]],
setup: (self) =>
self
.on("enter-notify-event", () => (revealer.reveal_child = true))
.on("leave-notify-event", () => (revealer.reveal_child = false))
.bind("visible", options.bar.position, "value", (v) => v !== "bottom"),
});
};

View File

@@ -87,6 +87,7 @@ export default {
next: "󰒭",
},
ui: {
colorpicker: "color-select-symbolic",
close: "window-close-symbolic",
info: "info-symbolic",
menu: "open-menu-symbolic",

View File

@@ -7,7 +7,7 @@ const PasswordEntry = () =>
Widget.Box({
children: [
Widget.Entry({
connections: [[Lockscreen, (entry) => (entry.text = ""), "lock"]],
setup: (self) => self.hook(Lockscreen, () => (self.text = ""), "lock"),
visibility: false,
placeholder_text: "Password",
on_accept: ({ text }) => Lockscreen.auth(text || ""),
@@ -17,9 +17,12 @@ const PasswordEntry = () =>
Widget.Spinner({
active: true,
vpack: "center",
connections: [
[Lockscreen, (w, auth) => (w.visible = auth), "authenticating"],
],
setup: (self) =>
self.hook(
Lockscreen,
(_, auth) => (self.visible = auth),
"authenticating",
),
}),
],
});
@@ -32,7 +35,8 @@ export default (monitor) => {
monitor,
layer: "overlay",
visible: false,
connections: [[Lockscreen, (w, lock) => (w.visible = lock), "lock"]],
setup: (self) =>
self.hook(Lockscreen, (_, lock) => (self.visible = lock), "lock"),
child: Widget.Box({
css: "min-width: 3000px; min-height: 2000px;",
class_name: "shader",

View File

@@ -1,6 +1,6 @@
import Applauncher from "./applauncher/Applauncher.js";
import Dashboard from "./dashboard/Dashboard.js";
import Desktop from "./desktop/Desktop.js";
import FloatingDock from "./dock/FloatingDock.js";
import Lockscreen from "./lockscreen/Lockscreen.js";
import Notifications from "./notifications/Notifications.js";
import OSD from "./osd/OSD.js";
@@ -15,20 +15,19 @@ import { init } from "./settings/setup.js";
import { forMonitors } from "./utils.js";
import { initWallpaper } from "./settings/wallpaper.js";
import options from "./options.js";
import Dock from "./dock/Dock.js";
initWallpaper();
const windows = () => [
forMonitors(Desktop),
forMonitors(FloatingDock),
forMonitors(Lockscreen),
forMonitors(Notifications),
forMonitors(OSD),
forMonitors(ScreenCorners),
forMonitors(TopBar),
Applauncher(),
Dashboard(),
Dock(),
Overview(),
PowerMenu(),
QuickSettings(),
@@ -39,8 +38,6 @@ const windows = () => [
export default {
onConfigParsed: init,
windows: windows().flat(1),
maxStreamVolume: 1.05,
cacheNotificationActions: false,
closeWindowDelay: {
quicksettings: options.transition.value,
dashboard: options.transition.value,

View File

@@ -4,15 +4,11 @@ import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
export default () =>
Widget.Icon({
class_name: "battery",
binds: [["icon", Battery, "icon-name"]],
connections: [
[
Battery,
(icon) => {
icon.toggleClassName("charging", Battery.charging);
icon.toggleClassName("charged", Battery.charged);
icon.toggleClassName("low", Battery.percent < 30);
},
],
],
icon: Battery.bind("icon_name"),
setup: (icon) =>
icon.hook(Battery, () => {
icon.toggleClassName("charging", Battery.charging);
icon.toggleClassName("charged", Battery.charged);
icon.toggleClassName("low", Battery.percent < 30);
}),
});

View File

@@ -1,5 +1,5 @@
import { clock } from "../variables.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import GLib from "gi://GLib";
/**
* @param {import('types/widgets/label').Props & {
@@ -7,20 +7,11 @@ import GLib from "gi://GLib";
* interval?: number,
* }} o
*/
export default ({
format = "%H:%M:%S %B %e. %A",
interval = 1000,
...rest
} = {}) =>
export default ({ format = "%H:%M:%S %B %e. %A", ...rest } = {}) =>
Widget.Label({
class_name: "clock",
label: clock.bind("value").transform((time) => {
return time.format(format) || "wrong format";
}),
...rest,
connections: [
[
interval,
(label) =>
(label.label =
GLib.DateTime.new_now_local().format(format) || "wrong format"),
],
],
});

View File

@@ -1,49 +1,48 @@
import Gtk from "gi://Gtk";
import { createCtor } from "resource:///com/github/Aylur/ags/widget.js";
import Gtk from "gi://Gtk?version=3.0";
import { subclass, register } from "resource:///com/github/Aylur/ags/widget.js";
import AgsLabel from "resource:///com/github/Aylur/ags/widgets/label.js";
import GObject from "gi://GObject";
export default createCtor(
class FontIcon extends AgsLabel {
static {
GObject.registerClass(this);
}
class FontIcon extends AgsLabel {
static {
register(this);
}
/** @param {string | import('types/widgets/label').Props & { icon?: string }} params */
constructor(params = "") {
// @ts-expect-error
const { icon = "", ...rest } = params;
/** @param {string | import('types/widgets/label').Props<any> & { icon?: string }} params */
constructor(params = "") {
// @ts-expect-error
const { icon = "", ...rest } = params;
super(typeof params === "string" ? {} : rest);
this.toggleClassName("font-icon");
super(typeof params === "string" ? {} : rest);
this.toggleClassName("font-icon");
if (typeof params === "object") this.icon = icon;
if (typeof params === "object") this.icon = icon;
if (typeof params === "string") this.icon = params;
}
if (typeof params === "string") this.icon = params;
}
get icon() {
return this.label;
}
set icon(icon) {
this.label = icon;
}
get icon() {
return this.label;
}
set icon(icon) {
this.label = icon;
}
get size() {
return this.get_style_context().get_property(
"font-size",
Gtk.StateFlags.NORMAL,
);
}
get size() {
return this.get_style_context().get_property(
"font-size",
Gtk.StateFlags.NORMAL,
);
}
/** @returns {[number, number]} */
vfunc_get_preferred_height() {
return [this.size, this.size];
}
/** @returns {[number, number]} */
vfunc_get_preferred_height() {
return [this.size, this.size];
}
/** @returns {[number, number]} */
vfunc_get_preferred_width() {
return [this.size, this.size];
}
},
);
/** @returns {[number, number]} */
vfunc_get_preferred_width() {
return [this.size, this.size];
}
}
export default subclass(FontIcon);

View File

@@ -6,8 +6,8 @@ import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
* indicator?: import('types/widgets/box').BoxProps['child']
* direction?: 'left' | 'right' | 'down' | 'up'
* duration?: number
* eventboxConnections?: import('types/widgets/box').BoxProps['connections']
* connections?: import('types/widgets/revealer').RevealerProps['connections']
* setupRevealer?: (rev: ReturnType<typeof Widget.Revealer>) => void
* setupEventBox?: (rev: ReturnType<typeof Widget.EventBox>) => void
* }} HoverRevealProps
*/
@@ -19,9 +19,8 @@ export default ({
child,
direction = "left",
duration = 300,
connections = [],
eventboxConnections = [],
binds = [],
setupEventBox,
setupRevealer,
...rest
}) => {
let open = false;
@@ -31,15 +30,14 @@ export default ({
const revealer = Widget.Revealer({
transition: `slide_${direction}`,
connections,
binds,
setup: setupRevealer,
transition_duration: duration,
child,
});
const eventbox = Widget.EventBox({
...rest,
connections: eventboxConnections,
setup: setupEventBox,
on_hover: () => {
if (open) return;

View File

@@ -4,23 +4,42 @@ import Widget from "resource:///com/github/Aylur/ags/widget.js";
import options from "../options.js";
import GObject from "gi://GObject";
class PopupWindow extends AgsWindow {
const keyGrabber = Widget.Window({
name: "key-grabber",
popup: true,
anchor: ["top", "left", "right", "bottom"],
css: "background-color: transparent;",
visible: false,
exclusivity: "ignore",
keymode: "on-demand",
layer: "top",
attribute: { list: [] },
setup: (self) =>
self.on("notify::visible", ({ visible }) => {
if (!visible)
self.attribute?.list.forEach((name) => App.closeWindow(name));
}),
child: Widget.EventBox({ vexpand: true }).on("button-press-event", () => {
App.closeWindow("key-grabber");
keyGrabber.attribute?.list.forEach((name) => App.closeWindow(name));
}),
});
// add before any PopupWindow is instantiated
App.addWindow(keyGrabber);
export class PopupWindow extends AgsWindow {
static {
GObject.registerClass(this);
}
/** @param {import('types/widgets/window').WindowProps & {
* name: string
* child: import('types/widgets/box').default
* transition?: import('types/widgets/revealer').RevealerProps['transition']
* }} o
*/
constructor({ name, child, transition = "none", visible = false, ...rest }) {
super({
...rest,
name,
popup: true,
focusable: true,
keymode: "exclusive",
layer: "overlay",
class_names: ["popup-window", name],
});
@@ -28,15 +47,11 @@ class PopupWindow extends AgsWindow {
this.revealer = Widget.Revealer({
transition,
child,
transitionDuration: options.transition.value,
connections: [
[
App,
(_, wname, visible) => {
if (wname === name) this.revealer.reveal_child = visible;
},
],
],
transition_duration: options.transition.value,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) this.revealer.reveal_child = visible;
}),
});
this.child = Widget.Box({
@@ -46,6 +61,9 @@ class PopupWindow extends AgsWindow {
this.show_all();
this.visible = visible;
keyGrabber.bind("visible", this, "visible");
keyGrabber.attribute?.list.push(name);
}
set transition(dir) {

View File

@@ -1,6 +1,10 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
/** @param {import('types/widgets/box').BoxProps & {
* width: number
* height: number
* }} o */
export default ({
height = 18,
width = 180,
@@ -27,31 +31,30 @@ export default ({
min-height: ${height}px;
`,
children: [fill],
setup: (progress) =>
(progress.setValue = (value) => {
if (value < 0) return;
attribute: (value) => {
if (value < 0) return;
const axis = vertical ? "height" : "width";
const axisv = vertical ? height : width;
const min = vertical ? width : height;
const preferred = (axisv - min) * value + min;
const axis = vertical ? "height" : "width";
const axisv = vertical ? height : width;
const min = vertical ? width : height;
const preferred = (axisv - min) * value + min;
if (!fill_size) {
fill_size = preferred;
fill.setCss(`min-${axis}: ${preferred}px;`);
return;
}
if (!fill_size) {
fill_size = preferred;
fill.setCss(`min-${axis}: ${preferred}px;`);
return;
}
const frames = 10;
const goal = preferred - fill_size;
const step = goal / frames;
const frames = 10;
const goal = preferred - fill_size;
const step = goal / frames;
for (let i = 0; i < frames; ++i) {
Utils.timeout(5 * i, () => {
fill_size += step;
fill.setCss(`min-${axis}: ${fill_size}px`);
});
}
}),
for (let i = 0; i < frames; ++i) {
Utils.timeout(5 * i, () => {
fill_size += step;
fill.setCss(`min-${axis}: ${fill_size}px`);
});
}
},
});
};

View File

@@ -1,19 +1,4 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import { subclass } from "resource:///com/github/Aylur/ags/widget.js";
import Gtk from "gi://Gtk";
import AgsWidget from "resource:///com/github/Aylur/ags/widgets/widget.js";
class RegularWindow extends AgsWidget(Gtk.Window, "RegularWindow") {
static {
AgsWidget.register(this);
}
/**
* @param {import('types/widgets/widget').BaseProps<
* RegularWindow, Gtk.Window.ConstructorProperties
* >} params */
constructor(params) {
// @ts-expect-error
super(params);
}
}
export default Widget.createCtor(RegularWindow);
export default subclass(Gtk.Window, "RegularWindow");

View File

@@ -11,14 +11,9 @@ export const CoverArt = (player, props) =>
Widget.Box({
...props,
class_name: "cover",
binds: [
[
"css",
player,
"cover-path",
(path) => `background-image: url("${path}")`,
],
],
css: player
.bind("cover_path")
.transform((p) => `background-image: url("${p}")`),
});
/**
@@ -29,16 +24,15 @@ export const BlurredCoverArt = (player, props) =>
Widget.Box({
...props,
class_name: "blurred-cover",
connections: [
[
setup: (self) =>
self.hook(
player,
(box) =>
blurImg(player.cover_path).then((img) => {
img && box.setCss(`background-image: url("${img}")`);
}),
"notify::cover-path",
],
],
),
});
/**
@@ -49,7 +43,7 @@ export const TitleLabel = (player, props) =>
Widget.Label({
...props,
class_name: "title",
binds: [["label", player, "track-title"]],
label: player.bind("track_title"),
});
/**
@@ -60,7 +54,7 @@ export const ArtistLabel = (player, props) =>
Widget.Label({
...props,
class_name: "artist",
binds: [["label", player, "track-artists", (a) => a.join(", ") || ""]],
label: player.bind("track_artists").transform((a) => a.join(", ") || ""),
});
/**
@@ -72,17 +66,13 @@ export const PlayerIcon = (player, { symbolic = true, ...props } = {}) =>
...props,
class_name: "player-icon",
tooltip_text: player.identity || "",
connections: [
[
player,
(icon) => {
const name = `${player.entry}${symbolic ? "-symbolic" : ""}`;
Utils.lookUpIcon(name)
? (icon.icon = name)
: (icon.icon = icons.mpris.fallback);
},
],
],
setup: (self) =>
self.hook(player, (icon) => {
const name = `${player.entry}${symbolic ? "-symbolic" : ""}`;
Utils.lookUpIcon(name)
? (icon.icon = name)
: (icon.icon = icons.mpris.fallback);
}),
});
/**
@@ -94,25 +84,18 @@ export const PositionSlider = (player, props) =>
...props,
class_name: "position-slider",
draw_value: false,
on_change: ({ value }) => {
player.position = player.length * value;
},
properties: [
[
"update",
(slider) => {
if (slider.dragging) return;
on_change: ({ value }) => (player.position = player.length * value),
setup: (self) => {
const update = () => {
if (self.dragging) return;
slider.visible = player.length > 0;
if (player.length > 0) slider.value = player.position / player.length;
},
],
],
connections: [
[player, (s) => s._update(s)],
[player, (s) => s._update(s), "position"],
[1000, (s) => s._update(s)],
],
self.visible = player.length > 0;
if (player.length > 0) self.value = player.position / player.length;
};
self.hook(player, update);
self.hook(player, update, "position");
self.poll(1000, update);
},
});
/** @param {number} length */
@@ -126,90 +109,73 @@ function lengthStr(length) {
/** @param {import('types/service/mpris').MprisPlayer} player */
export const PositionLabel = (player) =>
Widget.Label({
properties: [
[
"update",
(label, time) => {
player.length > 0
? (label.label = lengthStr(time || player.position))
: (label.visible = !!player);
},
],
],
connections: [
[player, (l, time) => l._update(l, time), "position"],
[1000, (l) => l._update(l)],
],
setup: (self) => {
const update = (_, time) => {
player.length > 0
? (self.label = lengthStr(time || player.position))
: (self.visible = !!player);
};
self.hook(player, update, "position");
self.poll(1000, update);
},
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const LengthLabel = (player) =>
Widget.Label({
connections: [
[
player,
(label) => {
player.length > 0
? (label.label = lengthStr(player.length))
: (label.visible = !!player);
},
],
],
label: player.bind("length").transform((l) => lengthStr(l)),
visible: player.bind("length").transform((l) => l > 0),
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const Slash = (player) =>
Widget.Label({
label: "/",
connections: [
[
player,
(label) => {
label.visible = player.length > 0;
},
],
],
visible: player.bind("length").transform((l) => l > 0),
});
/**
* @param {Object} o
* @param {import('types/service/mpris').MprisPlayer} o.player
* @param {import('types/widgets/stack').StackProps['items']} o.items
* @param {import('types/widgets/stack').StackProps['children']} o.children
* @param {'shuffle' | 'loop' | 'playPause' | 'previous' | 'next'} o.onClick
* @param {string} o.prop
* @param {string} o.canProp
* @param {any} o.cantValue
*/
const PlayerButton = ({ player, items, onClick, prop, canProp, cantValue }) =>
const PlayerButton = ({
player,
children,
onClick,
prop,
canProp,
cantValue,
}) =>
Widget.Button({
child: Widget.Stack({
items,
binds: [["shown", player, prop, (p) => `${p}`]],
}),
on_clicked: player[onClick].bind(player),
binds: [["visible", player, canProp, (c) => c !== cantValue]],
child: Widget.Stack({ children }).bind(
"shown",
player,
prop,
(p) => `${p}`,
),
on_clicked: () => player[onClick](),
visible: player.bind(canProp).transform((c) => c !== cantValue),
});
/** @param {import('types/service/mpris').MprisPlayer} player */
export const ShuffleButton = (player) =>
PlayerButton({
player,
items: [
[
"true",
Widget.Label({
class_name: "shuffle enabled",
label: icons.mpris.shuffle.enabled,
}),
],
[
"false",
Widget.Label({
class_name: "shuffle disabled",
label: icons.mpris.shuffle.disabled,
}),
],
],
children: {
true: Widget.Label({
class_name: "shuffle enabled",
label: icons.mpris.shuffle.enabled,
}),
false: Widget.Label({
class_name: "shuffle disabled",
label: icons.mpris.shuffle.disabled,
}),
},
onClick: "shuffle",
prop: "shuffle-status",
canProp: "shuffle-status",
@@ -220,29 +186,20 @@ export const ShuffleButton = (player) =>
export const LoopButton = (player) =>
PlayerButton({
player,
items: [
[
"None",
Widget.Label({
class_name: "loop none",
label: icons.mpris.loop.none,
}),
],
[
"Track",
Widget.Label({
class_name: "loop track",
label: icons.mpris.loop.track,
}),
],
[
"Playlist",
Widget.Label({
class_name: "loop playlist",
label: icons.mpris.loop.playlist,
}),
],
],
children: {
None: Widget.Label({
class_name: "loop none",
label: icons.mpris.loop.none,
}),
Track: Widget.Label({
class_name: "loop track",
label: icons.mpris.loop.track,
}),
Playlist: Widget.Label({
class_name: "loop playlist",
label: icons.mpris.loop.playlist,
}),
},
onClick: "loop",
prop: "loop-status",
canProp: "loop-status",
@@ -253,29 +210,20 @@ export const LoopButton = (player) =>
export const PlayPauseButton = (player) =>
PlayerButton({
player,
items: [
[
"Playing",
Widget.Label({
class_name: "playing",
label: icons.mpris.playing,
}),
],
[
"Paused",
Widget.Label({
class_name: "paused",
label: icons.mpris.paused,
}),
],
[
"Stopped",
Widget.Label({
class_name: "stopped",
label: icons.mpris.stopped,
}),
],
],
children: {
Playing: Widget.Label({
class_name: "playing",
label: icons.mpris.playing,
}),
Paused: Widget.Label({
class_name: "paused",
label: icons.mpris.paused,
}),
Stopped: Widget.Label({
class_name: "stopped",
label: icons.mpris.stopped,
}),
},
onClick: "playPause",
prop: "play-back-status",
canProp: "can-play",
@@ -286,15 +234,12 @@ export const PlayPauseButton = (player) =>
export const PreviousButton = (player) =>
PlayerButton({
player,
items: [
[
"true",
Widget.Label({
class_name: "previous",
label: icons.mpris.prev,
}),
],
],
children: {
true: Widget.Label({
class_name: "previous",
label: icons.mpris.prev,
}),
},
onClick: "previous",
prop: "can-go-prev",
canProp: "can-go-prev",
@@ -305,15 +250,12 @@ export const PreviousButton = (player) =>
export const NextButton = (player) =>
PlayerButton({
player,
items: [
[
"true",
Widget.Label({
class_name: "next",
label: icons.mpris.next,
}),
],
],
children: {
true: Widget.Label({
class_name: "next",
label: icons.mpris.next,
}),
},
onClick: "next",
prop: "can-go-next",
canProp: "can-go-next",

View File

@@ -39,14 +39,10 @@ const Popups = (parent) => {
});
};
return Widget.Box({
vertical: true,
connections: [
[Notifications, onNotified, "notified"],
[Notifications, onDismissed, "dismissed"],
[Notifications, (box, id) => onDismissed(box, id, true), "closed"],
],
});
return Widget.Box({ vertical: true })
.hook(Notifications, onNotified, "notified")
.hook(Notifications, onDismissed, "dismissed")
.hook(Notifications, (box, id) => onDismissed(box, id, true), "closed");
};
/** @param {import('types/widgets/revealer').RevealerProps['transition']} transition */
@@ -67,6 +63,6 @@ export default (monitor) =>
monitor,
name: `notifications${monitor}`,
class_name: "notifications",
binds: [["anchor", options.notifications.position]],
anchor: options.notifications.position.bind("value"),
child: PopupList(),
});

View File

@@ -10,56 +10,39 @@ export const OnScreenIndicator = ({ height = 300, width = 48 } = {}) =>
css: "padding: 1px;",
child: Widget.Revealer({
transition: "slide_left",
connections: [
[
Indicator,
(revealer, value) => {
revealer.reveal_child = value > -1;
},
],
],
setup: (self) =>
self.hook(Indicator, (_, value) => {
self.reveal_child = value > -1;
}),
child: Progress({
width,
height,
vertical: true,
connections: [
[Indicator, (progress, value) => progress.setValue(value)],
],
setup: (self) =>
self.hook(Indicator, (_, value) => self.attribute(value)),
child: Widget.Stack({
vpack: "start",
hpack: "center",
hexpand: false,
items: [
[
"true",
Widget.Icon({
hpack: "center",
size: width,
connections: [
[Indicator, (icon, _v, name) => (icon.icon = name || "")],
],
}),
],
[
"false",
FontIcon({
hpack: "center",
hexpand: true,
css: `font-size: ${width}px;`,
connections: [
[Indicator, (icon, _v, name) => (icon.icon = name || "")],
],
}),
],
],
connections: [
[
Indicator,
(stack, _v, name) => {
stack.shown = `${!!Utils.lookUpIcon(name)}`;
},
],
],
children: {
true: Widget.Icon({
hpack: "center",
size: width,
setup: (w) =>
w.hook(Indicator, (_, _v, name) => (w.icon = name || "")),
}),
false: FontIcon({
hpack: "center",
hexpand: true,
css: `font-size: ${width}px;`,
setup: (w) =>
w.hook(Indicator, (_, _v, name) => (w.label = name || "")),
}),
},
setup: (self) =>
self.hook(Indicator, (_, _v, name) => {
self.shown = `${!!Utils.lookUpIcon(name)}`;
}),
}),
}),
}),

View File

@@ -2,9 +2,10 @@ import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import { createSurfaceFromWidget, substitute } from "../utils.js";
import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk";
import Gtk from "gi://Gtk?version=3.0";
import options from "../options.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import icons from "../icons.js";
const SCALE = 0.08;
const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
@@ -13,7 +14,13 @@ const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
const dispatch = (args) => Hyprland.sendMessage(`dispatch ${args}`);
/** @param {string} str */
const icon = (str) => substitute(options.substitutions.icons, str);
const icon = (str) => {
const icon = substitute(options.substitutions.icons, str);
if (Utils.lookUpIcon(icon)) return icon;
console.warn("no icon", icon);
return icons.fallback.executable;
};
export default ({ address, size: [w, h], class: c, title }) =>
Widget.Button({
@@ -32,19 +39,19 @@ export default ({ address, size: [w, h], class: c, title }) =>
App.closeWindow("overview"),
),
setup: (btn) => {
btn.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK,
TARGET,
Gdk.DragAction.COPY,
);
btn.connect("drag-data-get", (_w, _c, data) =>
data.set_text(address, address.length),
);
btn.connect("drag-begin", (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn));
btn.toggleClassName("hidden", true);
});
btn.connect("drag-end", () => btn.toggleClassName("hidden", false));
},
setup: (btn) =>
btn
.on("drag-data-get", (_w, _c, data) =>
data.set_text(address, address.length),
)
.on("drag-begin", (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(btn));
btn.toggleClassName("hidden", true);
})
.on("drag-end", () => btn.toggleClassName("hidden", false))
.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK,
TARGET,
Gdk.DragAction.COPY,
),
});

View File

@@ -1,5 +1,3 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import PopupWindow from "../misc/PopupWindow.js";
import Workspace from "./Workspace.js";
@@ -8,15 +6,30 @@ import { range } from "../utils.js";
const ws = options.workspaces;
/** @param {import('types/widgets/box').default} box */
const Overview = () =>
Widget.Box({
children: [Workspace(0)], // for type infer
setup: (self) =>
Utils.idle(() => {
self.hook(ws, () => {
self.children = range(ws.value).map(Workspace);
update(self);
children(self);
});
self.hook(Hyprland, update);
self.hook(Hyprland, children, "notify::workspaces");
update(self);
children(self);
}),
});
/** @param {ReturnType<typeof Overview>} box */
const update = (box) => {
if (App.windows.has("overview") && !App.getWindow("overview")?.visible)
return;
if (!box.get_parent()?.visible) return;
Hyprland.sendMessage("j/clients")
.then((clients) => {
box.children.forEach((ws) => {
// @ts-expect-error
ws.attribute(JSON.parse(clients));
});
})
@@ -35,19 +48,5 @@ const children = (box) => {
export default () =>
PopupWindow({
name: "overview",
child: Widget.Box({
setup: update,
connections: [
[
ws,
(box) => {
box.children = range(ws.value).map(Workspace);
update(box);
children(box);
},
],
[Hyprland, update],
[Hyprland, children, "notify::workspaces"],
],
}),
child: Overview(),
});

View File

@@ -2,7 +2,7 @@ import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Gdk from "gi://Gdk";
import Gtk from "gi://Gtk";
import Gtk from "gi://Gtk?version=3.0";
import Client from "./Client.js";
const SCALE = 0.08;
@@ -22,14 +22,10 @@ export default (index) => {
min-width: ${3840 * SCALE}px;
min-height: ${2160 * SCALE}px;
`,
connections: [
[
Hyprland,
(box) => {
box.toggleClassName("active", Hyprland.active.workspace.id === index);
},
],
],
setup: (box) =>
box.hook(Hyprland, () => {
box.toggleClassName("active", Hyprland.active.workspace.id === index);
}),
child: Widget.EventBox({
hexpand: true,
vexpand: true,

View File

@@ -7,11 +7,13 @@ const Padding = (windowName) =>
class_name: "padding",
hexpand: true,
vexpand: true,
connections: [["button-press-event", () => App.toggleWindow(windowName)]],
setup: (w) =>
w.on("button-press-event", () => App.toggleWindow(windowName)),
});
/**
* @param {import('types/widgets/window').WindowProps & {
* @template {import('gi://Gtk?version=3.0').default.Widget} T
* @param {import('types/widgets/window').WindowProps<T> & {
* name: string
* child: import('types/widgets/box').default
* }} o
@@ -23,20 +25,20 @@ export default ({ name, child, ...rest }) =>
name,
visible: false,
popup: true,
focusable: true,
keymode: "on-demand",
setup() {
child.toggleClassName("window-content");
},
child: Widget.CenterBox({
class_name: "shader",
css: "min-width: 5000px; min-height: 3000px;",
children: [
Padding(name),
Widget.CenterBox({
vertical: true,
children: [Padding(name), child, Padding(name)],
}),
Padding(name),
],
start_widget: Padding(name),
end_widget: Padding(name),
center_widget: Widget.CenterBox({
vertical: true,
start_widget: Padding(name),
end_widget: Padding(name),
center_widget: child,
}),
}),
});

View File

@@ -17,7 +17,7 @@ export default () =>
children: [
Widget.Label({
class_name: "title",
binds: [["label", PowerMenu, "title"]],
label: PowerMenu.bind("title"),
}),
Widget.Label({
class_name: "desc",

View File

@@ -33,19 +33,15 @@ const Homogeneous = (toggles) =>
export default () =>
PopupWindow({
name: "quicksettings",
connections: [
[
options.bar.position,
(self) => {
self.anchor = ["right", options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
setup: (self) =>
self.hook(options.bar.position, () => {
self.anchor = ["right", options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
},
],
],
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
}),
child: Widget.Box({
vertical: true,
children: [
@@ -60,12 +56,8 @@ export default () =>
],
}),
Row(
[Homogeneous([/*NetworkToggle(),*/ BluetoothToggle()]), DND()],
[/*WifiSelection()*/ BluetoothDevices()],
),
Row(
[Homogeneous([/*ProfileToggle(),*/ ThemeToggle()]), MicMute()],
[/*ProfileSelector(),*/ ThemeSelector()],
[Homogeneous([ThemeToggle(), BluetoothToggle()]), MicMute()],
[ThemeSelector(), BluetoothDevices()],
),
Media(),
],

View File

@@ -18,30 +18,23 @@ App.connect("window-toggled", (_, name, visible) => {
export const Arrow = (name, activate) => {
let deg = 0;
let iconOpened = false;
const icon = Widget.Icon(icons.ui.arrow.right).hook(opened, () => {
if (
(opened.value === name && !iconOpened) ||
(opened.value !== name && iconOpened)
) {
const step = opened.value === name ? 10 : -10;
iconOpened = !iconOpened;
for (let i = 0; i < 9; ++i) {
Utils.timeout(15 * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
}
});
return Widget.Button({
child: Widget.Icon({
icon: icons.ui.arrow.right,
connections: [
[
opened,
(icon) => {
if (
(opened.value === name && !iconOpened) ||
(opened.value !== name && iconOpened)
) {
const step = opened.value === name ? 10 : -10;
iconOpened = !iconOpened;
for (let i = 0; i < 9; ++i) {
Utils.timeout(15 * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
}
},
],
],
}),
child: icon,
on_clicked: () => {
opened.value = opened.value === name ? "" : name;
if (typeof activate === "function") activate();
@@ -70,14 +63,10 @@ export const ArrowToggleButton = ({
}) =>
Widget.Box({
class_name: "toggle-button",
connections: [
[
service,
(box) => {
box.toggleClassName("active", condition());
},
],
],
setup: (self) =>
self.hook(service, () => {
self.toggleClassName("active", condition());
}),
children: [
Widget.Button({
child: Widget.Box({
@@ -108,7 +97,7 @@ export const ArrowToggleButton = ({
export const Menu = ({ name, icon, title, content }) =>
Widget.Revealer({
transition: "slide_down",
binds: [["reveal-child", opened, "value", (v) => v === name]],
reveal_child: opened.bind().transform((v) => v === name),
child: Widget.Box({
class_names: ["menu", name],
vertical: true,
@@ -136,14 +125,10 @@ export const SimpleToggleButton = ({
}) =>
Widget.Button({
class_name: "simple-toggle",
connections: [
[
service,
(box) => {
box.toggleClassName("active", condition());
},
],
],
setup: (self) =>
self.hook(service, () => {
self.toggleClassName("active", condition());
}),
child: icon,
on_clicked: toggle,
});

View File

@@ -8,10 +8,10 @@ export const ProfileToggle = () =>
ArrowToggleButton({
name: "asusctl-profile",
icon: Widget.Icon({
binds: [["icon", Asusctl, "profile", (p) => icons.asusctl.profile[p]]],
icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
}),
label: Widget.Label({
binds: [["label", Asusctl, "profile"]],
label: Asusctl.bind("profile"),
}),
connection: [Asusctl, () => Asusctl.profile !== "Balanced"],
activate: () => Asusctl.setProfile("Quiet"),
@@ -23,7 +23,7 @@ export const ProfileSelector = () =>
Menu({
name: "asusctl-profile",
icon: Widget.Icon({
binds: [["icon", Asusctl, "profile", (p) => icons.asusctl.profile[p]]],
icon: Asusctl.bind("profile").transform((p) => icons.asusctl.profile[p]),
}),
title: Widget.Label("Profile Selector"),
content: [

View File

@@ -7,41 +7,31 @@ export const BluetoothToggle = () =>
ArrowToggleButton({
name: "bluetooth",
icon: Widget.Icon({
connections: [
[
Bluetooth,
(icon) => {
icon.icon = Bluetooth.enabled
? icons.bluetooth.enabled
: icons.bluetooth.disabled;
},
],
],
icon: Bluetooth.bind("enabled").transform(
(p) => icons.bluetooth[p ? "enabled" : "disabled"],
),
}),
label: Widget.Label({
truncate: "end",
connections: [
[
Bluetooth,
(label) => {
if (!Bluetooth.enabled) return (label.label = "Disabled");
setup: (self) =>
self.hook(Bluetooth, () => {
if (!Bluetooth.enabled) return (self.label = "Disabled");
if (Bluetooth.connectedDevices.length === 0)
return (label.label = "Not Connected");
if (Bluetooth.connected_devices.length === 0)
return (self.label = "Not Connected");
if (Bluetooth.connectedDevices.length === 1)
return (label.label = Bluetooth.connectedDevices[0].alias);
if (Bluetooth.connected_devices.length === 1)
return (self.label = Bluetooth.connected_devices[0].alias);
label.label = `${Bluetooth.connectedDevices.length} Connected`;
},
],
],
self.label = `${Bluetooth.connected_devices.length} Connected`;
}),
}),
connection: [Bluetooth, () => Bluetooth.enabled],
deactivate: () => (Bluetooth.enabled = false),
activate: () => (Bluetooth.enabled = true),
});
/** @param {import('types/service/bluetooth').BluetoothDevice} device */
const DeviceItem = (device) =>
Widget.Box({
children: [
@@ -49,26 +39,20 @@ const DeviceItem = (device) =>
Widget.Label(device.name),
Widget.Label({
label: `${device.battery_percentage}%`,
binds: [["visible", device, "battery-percentage", (p) => p > 0]],
visible: device.bind("battery_percentage").transform((p) => p > 0),
}),
Widget.Box({ hexpand: true }),
Widget.Spinner({
binds: [
["active", device, "connecting"],
["visible", device, "connecting"],
],
active: device.bind("connecting"),
visible: device.bind("connecting"),
}),
Widget.Switch({
active: device.connected,
binds: [["visible", device, "connecting", (c) => !c]],
connections: [
[
"notify::active",
({ active }) => {
device.setConnection(active);
},
],
],
visible: device.bind("connecting").transform((p) => !p),
setup: (self) =>
self.on("notify::active", () => {
device.setConnection(self.active);
}),
}),
],
});
@@ -82,14 +66,9 @@ export const BluetoothDevices = () =>
Widget.Box({
hexpand: true,
vertical: true,
binds: [
[
"children",
Bluetooth,
"devices",
(ds) => ds.filter((d) => d.name).map(DeviceItem),
],
],
children: Bluetooth.bind("devices").transform((ds) =>
ds.filter((d) => d.name).map(DeviceItem),
),
}),
],
});

View File

@@ -1,15 +1,12 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import Brightness from "../../services/brightness.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
const CanSupportBrightness = () => Utils.exec("which gbmonctl");
const BrightnessSlider = () =>
Widget.Slider({
draw_value: false,
hexpand: true,
binds: [["value", Brightness, "screen"]],
value: Brightness.bind("screen"),
on_change: ({ value }) => (Brightness.screen = value),
});
@@ -18,14 +15,9 @@ export default () =>
children: [
Widget.Button({
child: Widget.Icon(icons.brightness.indicator),
binds: [
[
"tooltip-text",
Brightness,
"screen",
(v) => `Screen Brightness: ${Math.floor(v * 100)}%`,
],
],
tooltip_text: Brightness.bind("screen").transform(
(v) => `Screen Brightness: ${Math.floor(v * 100)}%`,
),
}),
BrightnessSlider(),
],

View File

@@ -6,17 +6,9 @@ import { SimpleToggleButton } from "../ToggleButton.js";
export default () =>
SimpleToggleButton({
icon: Widget.Icon({
connections: [
[
Notifications,
(icon) => {
icon.icon = Notifications.dnd
? icons.notifications.silent
: icons.notifications.noisy;
},
"notify::dnd",
],
],
icon: Notifications.bind("dnd").transform(
(dnd) => icons.notifications[dnd ? "silent" : "noisy"],
),
}),
toggle: () => (Notifications.dnd = !Notifications.dnd),
connection: [Notifications, () => Notifications.dnd],

View File

@@ -6,6 +6,7 @@ import Avatar from "../../misc/Avatar.js";
import icons from "../../icons.js";
import { openSettings } from "../../settings/theme.js";
import { uptime } from "../../variables.js";
import DND from "./DND.js";
export default () =>
Widget.Box({
@@ -20,16 +21,17 @@ export default () =>
/*Widget.Box({
class_name: "battery horizontal",
children: [
Widget.Icon({ binds: [["icon", Battery, "icon-name"]] }),
Widget.Icon({ icon: Battery.bind("icon_name") }),
Widget.Label({
binds: [["label", Battery, "percent", (p) => `${p}%`]],
label: Battery.bind("percent").transform((p) => `${p}%`),
}),
],
}),
}),*/
DND(),
Widget.Label({
class_name: "uptime",
binds: [["label", uptime, "value", (v) => `up: ${v}`]],
}),*/
label: uptime.bind().transform((v) => `up: ${v}`),
}),
Widget.Button({
on_clicked: openSettings,
child: Widget.Icon(icons.ui.settings),

View File

@@ -7,31 +7,29 @@ import options from "../../options.js";
const Footer = (player) =>
Widget.CenterBox({
class_name: "footer-box",
children: [
Widget.Box({
class_name: "position",
children: [
mpris.PositionLabel(player),
mpris.Slash(player),
mpris.LengthLabel(player),
],
}),
Widget.Box({
class_name: "controls",
children: [
mpris.ShuffleButton(player),
mpris.PreviousButton(player),
mpris.PlayPauseButton(player),
mpris.NextButton(player),
mpris.LoopButton(player),
],
}),
mpris.PlayerIcon(player, {
symbolic: false,
hexpand: true,
hpack: "end",
}),
],
start_widget: Widget.Box({
class_name: "position",
children: [
mpris.PositionLabel(player),
mpris.Slash(player),
mpris.LengthLabel(player),
],
}),
center_widget: Widget.Box({
class_name: "controls",
children: [
mpris.ShuffleButton(player),
mpris.PreviousButton(player),
mpris.PlayPauseButton(player),
mpris.NextButton(player),
mpris.LoopButton(player),
],
}),
end_widget: mpris.PlayerIcon(player, {
symbolic: false,
hexpand: true,
hpack: "end",
}),
});
/** @param {import('types/service/mpris').MprisPlayer} player */
@@ -84,23 +82,10 @@ export default () =>
Widget.Box({
vertical: true,
class_name: "media vertical",
connections: [
[
"draw",
(self) => {
self.visible = Mpris.players.length > 0;
},
],
],
binds: [
[
"children",
Mpris,
"players",
(ps) =>
ps
.filter((p) => !options.mpris.black_list.value.includes(p.identity))
.map(PlayerBox),
],
],
visible: Mpris.bind("players").transform((p) => p.length > 0),
children: Mpris.bind("players").transform((ps) =>
ps
.filter((p) => !options.mpris.black_list.value.includes(p.identity))
.map(PlayerBox),
),
});

View File

@@ -5,19 +5,15 @@ import { SimpleToggleButton } from "../ToggleButton.js";
export default () =>
SimpleToggleButton({
icon: Widget.Icon({
connections: [
[
Audio,
(icon) => {
icon.icon = Audio.microphone?.is_muted
? icons.audio.mic.muted
: icons.audio.mic.high;
},
"microphone-changed",
],
],
}),
icon: Widget.Icon().hook(
Audio,
(self) => {
self.icon = Audio.microphone?.is_muted
? icons.audio.mic.muted
: icons.audio.mic.high;
},
"microphone-changed",
),
toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted),
connection: [Audio, () => Audio.microphone?.is_muted],
connection: [Audio, () => Audio.microphone?.is_muted || false],
});

View File

@@ -9,25 +9,13 @@ export const NetworkToggle = () =>
ArrowToggleButton({
name: "network",
icon: Widget.Icon({
connections: [
[
Network,
(icon) => {
icon.icon = Network.wifi.icon_name || "";
},
],
],
icon: Network.wifi.bind("icon_name"),
}),
label: Widget.Label({
truncate: "end",
connections: [
[
Network,
(label) => {
label.label = Network.wifi.ssid || "Not Connected";
},
],
],
label: Network.wifi
.bind("ssid")
.transform((ssid) => ssid || "Not Connected"),
}),
connection: [Network, () => Network.wifi.enabled],
deactivate: () => (Network.wifi.enabled = false),
@@ -41,24 +29,17 @@ export const WifiSelection = () =>
Menu({
name: "network",
icon: Widget.Icon({
connections: [
[
Network,
(icon) => {
icon.icon = Network.wifi.icon_name;
},
],
],
icon: Network.wifi.bind("icon_name"),
}),
title: Widget.Label("Wifi Selection"),
content: [
Widget.Box({
vertical: true,
connections: [
[
setup: (self) =>
self.hook(
Network,
(box) =>
(box.children = Network.wifi?.access_points.map((ap) =>
() =>
(self.children = Network.wifi?.access_points.map((ap) =>
Widget.Button({
on_clicked: () =>
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`),
@@ -76,8 +57,7 @@ export const WifiSelection = () =>
}),
}),
)),
],
],
),
}),
Widget.Separator(),
Widget.Button({

View File

@@ -8,8 +8,8 @@ import { setTheme, openSettings } from "../../settings/theme.js";
export const ThemeToggle = () =>
ArrowToggleButton({
name: "theme",
icon: Widget.Label({ binds: [["label", options.theme.icon]] }),
label: Widget.Label({ binds: [["label", options.theme.name]] }),
icon: Widget.Label().bind("label", options.theme.icon),
label: Widget.Label().bind("label", options.theme.name),
connection: [opened, () => opened.value === "theme"],
activate: () => opened.setValue("theme"),
activateOnArrow: false,
@@ -19,9 +19,7 @@ export const ThemeToggle = () =>
export const ThemeSelector = () =>
Menu({
name: "theme",
icon: Widget.Label({
binds: [["label", options.theme.icon]],
}),
icon: Widget.Label().bind("label", options.theme.icon),
title: Widget.Label("Theme Selector"),
content: [
...themes.map(({ name, icon }) =>
@@ -35,9 +33,9 @@ export const ThemeSelector = () =>
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
binds: [
["visible", options.theme.name, "value", (v) => v === name],
],
visible: options.theme.name
.bind("value")
.transform((v) => v === name),
}),
],
}),

View File

@@ -11,25 +11,13 @@ import { Menu } from "../ToggleButton.js";
const VolumeIndicator = (type = "speaker") =>
Widget.Button({
on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted),
child: Widget.Icon({
connections: [
[
Audio,
(icon) => {
if (!Audio[type]) return;
child: Widget.Icon().hook(Audio[type], (icon) => {
icon.icon =
type === "speaker"
? getAudioTypeIcon(Audio[type].icon_name || "")
: icons.audio.mic.high;
icon.icon =
type === "speaker"
? getAudioTypeIcon(Audio[type].icon_name || "")
: icons.audio.mic.high;
icon.tooltip_text = `Volume ${Math.floor(
Audio[type].volume * 100,
)}%`;
},
`${type}-changed`,
],
],
icon.tooltip_text = `Volume ${Math.floor(Audio[type].volume * 100)}%`;
}),
});
@@ -39,15 +27,10 @@ const VolumeSlider = (type = "speaker") =>
hexpand: true,
draw_value: false,
on_change: ({ value }) => (Audio[type].volume = value),
connections: [
[
Audio,
(slider) => {
slider.value = Audio[type]?.volume;
},
`${type}-changed`,
],
],
setup: (self) =>
self.hook(Audio[type], () => {
self.value = Audio[type].volume || 0;
}),
});
export const Volume = () =>
@@ -62,14 +45,7 @@ export const Volume = () =>
Widget.Box({
vpack: "center",
child: Arrow("app-mixer"),
connections: [
[
Audio,
(box) => {
box.visible = Audio.apps.length > 0;
},
],
],
visible: Audio.bind("apps").transform((a) => a.length > 0),
}),
],
});
@@ -77,7 +53,7 @@ export const Volume = () =>
export const Microhone = () =>
Widget.Box({
class_name: "slider horizontal",
binds: [["visible", Audio, "recorders", (r) => r.length > 0]],
visible: Audio.bind("recorders").transform((a) => a.length > 0),
children: [VolumeIndicator("microphone"), VolumeSlider("microphone")],
});
@@ -88,17 +64,10 @@ const MixerItem = (stream) =>
class_name: "mixer-item horizontal",
children: [
Widget.Icon({
binds: [["tooltipText", stream, "name"]],
connections: [
[
stream,
(icon) => {
icon.icon = Utils.lookUpIcon(stream.name || "")
? stream.name || ""
: icons.mpris.fallback;
},
],
],
tooltip_text: stream.bind("name").transform((n) => n || ""),
icon: stream.bind("name").transform((n) => {
return Utils.lookUpIcon(n || "") ? n || "" : icons.mpris.fallback;
}),
}),
Widget.Box({
vertical: true,
@@ -106,26 +75,19 @@ const MixerItem = (stream) =>
Widget.Label({
xalign: 0,
truncate: "end",
binds: [["label", stream, "description"]],
label: stream.bind("description").transform((d) => d || ""),
}),
Widget.Slider({
hexpand: true,
draw_value: false,
binds: [["value", stream, "volume"]],
value: stream.bind("volume"),
on_change: ({ value }) => (stream.volume = value),
}),
],
}),
Widget.Label({
xalign: 1,
connections: [
[
stream,
(l) => {
l.label = `${Math.floor(stream.volume * 100)}%`;
},
],
],
label: stream.bind("volume").transform((v) => `${Math.floor(v * 100)}`),
}),
],
});
@@ -148,7 +110,9 @@ const SinkItem = (stream) =>
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
binds: [["visible", Audio, "speaker", (s) => s === stream]],
visible: Audio.speaker
.bind("stream")
.transform((s) => s === stream.stream),
}),
],
}),
@@ -171,7 +135,7 @@ export const AppMixer = () =>
content: [
Widget.Box({
vertical: true,
binds: [["children", Audio, "apps", (a) => a.map(MixerItem)]],
children: Audio.bind("apps").transform((a) => a.map(MixerItem)),
}),
Widget.Separator(),
SettingsButton(),
@@ -186,7 +150,7 @@ export const SinkSelector = () =>
content: [
Widget.Box({
vertical: true,
binds: [["children", Audio, "speakers", (s) => s.map(SinkItem)]],
children: Audio.bind("speakers").transform((a) => a.map(SinkItem)),
}),
Widget.Separator(),
SettingsButton(),

View File

@@ -10,50 +10,49 @@ const Corner = (place) =>
vexpand: true,
hpack: place.includes("left") ? "start" : "end",
vpack: place.includes("top") ? "start" : "end",
connections: [
[
options.radii,
(self) => {
setup: (self) =>
self
.hook(options.radii, () => {
const r = options.radii.value * 2;
self.set_size_request(r, r);
},
],
],
setup: (self) =>
self.connect("draw", (self, cr) => {
const context = self.get_style_context();
const c = context.get_property(
"background-color",
Gtk.StateFlags.NORMAL,
);
const r = context.get_property("border-radius", Gtk.StateFlags.NORMAL);
})
.connect("draw", (self, cr) => {
const context = self.get_style_context();
const c = context.get_property(
"background-color",
Gtk.StateFlags.NORMAL,
);
const r = context.get_property(
"border-radius",
Gtk.StateFlags.NORMAL,
);
switch (place) {
case "topleft":
cr.arc(r, r, r, Math.PI, (3 * Math.PI) / 2);
cr.lineTo(0, 0);
break;
switch (place) {
case "topleft":
cr.arc(r, r, r, Math.PI, (3 * Math.PI) / 2);
cr.lineTo(0, 0);
break;
case "topright":
cr.arc(0, r, r, (3 * Math.PI) / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case "topright":
cr.arc(0, r, r, (3 * Math.PI) / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case "bottomleft":
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case "bottomleft":
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case "bottomright":
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
case "bottomright":
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
}),
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
}),
});
/** @type {Array<'topleft' | 'topright' | 'bottomleft' | 'bottomright'>} */
@@ -70,7 +69,7 @@ export default (monitor) =>
place.includes("top") ? "top" : "bottom",
place.includes("right") ? "right" : "left",
],
binds: [["visible", options.desktop.screen_corners]],
visible: options.desktop.screen_corners.bind("value"),
child: Corner(place),
}),
);

View File

@@ -13,7 +13,7 @@ class Asusctl extends Service {
);
}
profiles = Object.freeze(["Performance", "Balanced", "Quiet"]);
profiles = /** @type {const} */ (["Performance", "Balanced", "Quiet"]);
#profile = "Balanced";
#mode = "Hyprid";

View File

@@ -1,8 +1,8 @@
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import { Variable } from "resource:///com/github/Aylur/ags/variable.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
import { dependencies } from "../utils.js";
import icons from "../icons.js";
const COLORS_CACHE = Utils.CACHE_DIR + "/colorpicker.json";
@@ -58,15 +58,15 @@ class Colors extends Service {
);
}
this.#notifID = Notifications.Notify(
"Color Picker",
this.#notifID,
"color-select-symbolic",
color,
"",
[],
{},
);
const n = await Utils.notify({
id: this.#notifID,
iconName: icons.ui.colorpicker,
summary: color,
actions: {
Copy: () => this.wlCopy(color),
},
});
this.#notifID = n.id;
}
}

View File

@@ -21,7 +21,7 @@ showSearch.connect("changed", ({ value }) => {
/** @param {import('./option.js').Opt<string>} opt */
const EnumSetter = (opt) => {
const lbl = Widget.Label({ binds: [["label", opt]] });
const lbl = Widget.Label().bind("label", opt);
const step = (dir = 1) => {
const i = opt.enums.findIndex((i) => i === lbl.label);
opt.setValue(
@@ -57,51 +57,42 @@ const Setter = (opt) => {
setup(self) {
self.set_range(0, 1000);
self.set_increments(1, 5);
self.on("value-changed", () => opt.setValue(self.value, true));
self.hook(opt, () => (self.value = opt.value));
},
connections: [
["value-changed", (self) => opt.setValue(self.value, true)],
[opt, (self) => (self.value = opt.value)],
],
});
case "float":
case "object":
return Widget.Entry({
on_accept: (self) => opt.setValue(JSON.parse(self.text || ""), true),
connections: [[opt, (self) => (self.text = JSON.stringify(opt.value))]],
setup: (self) =>
self.hook(opt, () => (self.text = JSON.stringify(opt.value))),
});
case "string":
return Widget.Entry({
on_accept: (self) => opt.setValue(self.text, true),
connections: [[opt, (self) => (self.text = opt.value)]],
setup: (self) => self.hook(opt, () => (self.text = opt.value)),
});
case "enum":
return EnumSetter(opt);
case "boolean":
return Widget.Switch({
connections: [
["notify::active", (self) => opt.setValue(self.active, true)],
[opt, (self) => (self.active = opt.value)],
],
});
return Widget.Switch()
.on("notify::active", (self) => opt.setValue(self.active, true))
.hook(opt, (self) => (self.active = opt.value));
case "img":
return Widget.FileChooserButton({
connections: [
[
"selection-changed",
(self) => {
opt.setValue(self.get_uri()?.replace("file://", ""), true);
},
],
],
return Widget.FileChooserButton().on("selection-changed", (self) => {
opt.setValue(self.get_uri()?.replace("file://", ""), true);
});
case "font":
return Widget.FontButton({
show_size: false,
use_size: false,
connections: [
["notify::font", ({ font }) => opt.setValue(font, true)],
[opt, (self) => (self.font = opt.value)],
],
setup: (self) =>
self
.on("notify::font", ({ font }) => opt.setValue(font, true))
.hook(opt, () => (self.font = opt.value)),
});
default:
return Widget.Label({
@@ -114,7 +105,7 @@ const Setter = (opt) => {
const Row = (opt) =>
Widget.Box({
class_name: "row",
setup: (self) => (self.opt = opt),
attribute: opt,
children: [
Widget.Box({
vertical: true,
@@ -161,19 +152,15 @@ const Page = (category) =>
child: Widget.Box({
class_name: "page-content vertical",
vertical: true,
connections: [
[
search,
(self) => {
for (const child of self.children) {
child.visible =
child.opt.id.includes(search.value) ||
child.opt.title.includes(search.value) ||
child.opt.note.includes(search.value);
}
},
],
],
setup: (self) =>
self.hook(search, () => {
for (const child of self.children) {
child.visible =
child.attribute.id.includes(search.value) ||
child.attribute.title.includes(search.value) ||
child.attribute.note.includes(search.value);
}
}),
children: optionsList
.filter((opt) => opt.category.includes(category))
.map(Row),
@@ -181,7 +168,7 @@ const Page = (category) =>
});
const sidebar = Widget.Revealer({
binds: [["reveal-child", search, "value", (v) => !v]],
reveal_child: search.bind().transform((v) => !v),
transition: "slide_right",
child: Widget.Box({
hexpand: false,
@@ -213,14 +200,9 @@ const sidebar = Widget.Revealer({
Widget.Button({
label: (icons.dialog[name] || "") + " " + name,
xalign: 0,
binds: [
[
"class-name",
currentPage,
"value",
(v) => (v === name ? "active" : ""),
],
],
class_name: currentPage
.bind()
.transform((v) => `${v === name ? "active" : ""}`),
on_clicked: () => currentPage.setValue(name),
}),
),
@@ -254,21 +236,15 @@ const sidebar = Widget.Revealer({
const searchEntry = Widget.Revealer({
transition: "slide_down",
binds: [
["reveal-child", showSearch],
["transition-duration", options.transition],
],
reveal_child: showSearch.bind(),
transition_duration: options.transition.bind("value"),
child: Widget.Entry({
connections: [
[
showSearch,
(self) => {
if (!showSearch.value) self.text = "";
setup: (self) =>
self.hook(showSearch, () => {
if (!showSearch.value) self.text = "";
if (showSearch.value) self.grab_focus();
},
],
],
if (showSearch.value) self.grab_focus();
}),
hexpand: true,
class_name: "search",
placeholder_text: "Search Options",
@@ -279,41 +255,36 @@ const searchEntry = Widget.Revealer({
const categoriesStack = Widget.Stack({
transition: "slide_left_right",
items: categories.map((name) => [name, Page(name)]),
binds: [
["shown", currentPage],
["visible", search, "value", (v) => !v],
],
children: categories.reduce((obj, name) => {
obj[name] = Page(name);
return obj;
}, {}),
shown: currentPage.bind(),
visible: search.bind().transform((v) => !v),
});
const searchPage = Widget.Box({
binds: [["visible", search, "value", (v) => !!v]],
visible: search.bind().transform((v) => !!v),
child: Page(""),
});
export default RegularWindow({
name: "settings-dialog",
title: "Settings",
setup: (win) => win.set_default_size(800, 500),
connections: [
[
"delete-event",
(win) => {
setup: (win) =>
win
.on("delete-event", () => {
win.hide();
return true;
},
],
[
"key-press-event",
(self, event) => {
})
.on("key-press-event", (_, event) => {
if (event.get_keyval()[1] === imports.gi.Gdk.KEY_Escape) {
self.text = "";
showSearch.setValue(false);
search.setValue("");
}
},
],
],
})
.set_default_size(800, 500),
child: Widget.Box({
children: [
sidebar,

View File

@@ -15,6 +15,9 @@ export async function globals() {
globalThis.indicator = (
await import("../services/onScreenIndicator.js")
).default;
globalThis.app = (
await import("resource:///com/github/Aylur/ags/app.js")
).default;
Mpris.players.forEach((player) => {
player.connect("changed", (player) => {

View File

@@ -5,7 +5,7 @@ import {
} from "resource:///com/github/Aylur/ags/utils.js";
import { exec } from "resource:///com/github/Aylur/ags/utils.js";
import options from "../options.js";
import Service, { Binding } from "resource:///com/github/Aylur/ags/service.js";
import Service from "resource:///com/github/Aylur/ags/service.js";
import { reloadScss } from "./scss.js";
import { setupHyprland } from "./hyprland.js";
const CACHE_FILE = CACHE_DIR + "/options.json";

View File

@@ -1,7 +1,6 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import { getOptions } from "./option.js";
import { dependencies } from "../utils.js";
export function scssWatcher() {
return Utils.subprocess(
@@ -26,8 +25,6 @@ export function scssWatcher() {
* options.bar.style.value => $bar-style
*/
export async function reloadScss() {
if (!dependencies(["sassc"])) return;
const opts = getOptions();
const vars = opts.map((opt) => {
if (opt.scss === "exclude") return "";

View File

@@ -8,7 +8,7 @@ import { wallpaper } from "./wallpaper.js";
import { hyprlandInit, setupHyprland } from "./hyprland.js";
import { globals } from "./globals.js";
import { showAbout } from "../about/about.js";
import Gtk from "gi://Gtk";
import Gtk from "gi://Gtk?version=3.0";
export function init() {
notificationBlacklist();

View File

@@ -32,7 +32,7 @@ export function forMonitors(widget) {
}
/**
* @param {import('gi://Gtk').Gtk.Widget} widget
* @param {import('gi://Gtk?version=3.0').default.Widget} widget
* @returns {any} - missing cairo type
*/
export function createSurfaceFromWidget(widget) {

View File

@@ -4,6 +4,10 @@ import options from "./options.js";
const intval = options.systemFetchInterval;
export const clock = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
});
export const uptime = Variable("", {
poll: [
60_000,

View File

@@ -1,11 +1,13 @@
{
"name": "ags-dotfiles",
"version": "1.6.4",
"version": "1.7.5",
"description": "My config files for AGS",
"main": "config.js",
"scripts": {
"check": "tsc --noEmit",
"lint": "eslint . --fix",
"stylelint": "stylelint ./scss --fix"
"stylelint": "stylelint ./scss --fix",
"format": "prettier --write ."
},
"repository": {
"type": "git",
@@ -18,10 +20,16 @@
"homepage": "https://github.com/Aylur/dotfiles#readme",
"kofi": "https://ko-fi.com/aylur",
"devDependencies": {
"@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.7",
"@girs/gobject-2.0": "^2.78.0-3.2.7",
"@girs/gtk-3.0": "^3.24.38-3.2.7",
"@girs/gvc-1.0": "^1.0.0-3.2.7",
"@girs/nm-1.0": "^1.43.1-3.1.0"
"@girs/dbusmenugtk3-0.4": "^0.4.0-3.2.0",
"@girs/gobject-2.0": "^2.76.1-3.2.3",
"@girs/gtk-3.0": "^3.24.39-3.2.2",
"@girs/gvc-1.0": "^1.0.0-3.1.0",
"@girs/nm-1.0": "^1.43.1-3.1.0",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"eslint": "^8.44.0",
"prettier": "^3.2.5",
"stylelint-config-standard-scss": "^10.0.0",
"typescript": "^5.3.3"
}
}

View File

@@ -8,7 +8,7 @@
"strict": true,
"noImplicitAny": false,
"baseUrl": ".",
"typeRoots": ["./types/ags.d.ts", "./node_modules/@girs"],
"typeRoots": ["./types"],
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}

View File

@@ -160,6 +160,7 @@
brightnessctl
ydotool
kitty
hyprpicker
]}";
};
Install = {