testing ags
This commit is contained in:
62
home/core/gui/ags/windows/bar/main.js
Normal file
62
home/core/gui/ags/windows/bar/main.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { App, Widget } from "../../imports.js";
|
||||
import Battery from "./modules/battery.js";
|
||||
import Bluetooth from "./modules/bluetooth.js";
|
||||
import Date from "./modules/date.js";
|
||||
import Music from "./modules/music.js";
|
||||
import Net from "./modules/net.js";
|
||||
import CpuRam from "./modules/cpu_ram.js";
|
||||
import Tray from "./modules/tray.js";
|
||||
import Workspaces from "./modules/workspaces.js";
|
||||
|
||||
const SystemInfo = () =>
|
||||
Widget.EventBox({
|
||||
className: "system-menu-toggler",
|
||||
onPrimaryClick: () => App.toggleWindow("system-menu"),
|
||||
|
||||
child: Widget.Box({
|
||||
children: [Net(), Bluetooth(), Battery()],
|
||||
}),
|
||||
}).hook(App, (self, window, visible) => {
|
||||
if (window === "system-menu") {
|
||||
self.toggleClassName("active", visible);
|
||||
}
|
||||
});
|
||||
|
||||
const Start = () =>
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
hpack: "start",
|
||||
children: [
|
||||
Workspaces(),
|
||||
// Indicators
|
||||
],
|
||||
});
|
||||
|
||||
const Center = () =>
|
||||
Widget.Box({
|
||||
children: [Music()],
|
||||
});
|
||||
|
||||
const End = () =>
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
|
||||
children: [Tray(), CpuRam(), SystemInfo(), Date()],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Window({
|
||||
monitor: 0,
|
||||
name: `bar`,
|
||||
anchor: ["bottom", "left", "right"],
|
||||
exclusivity: "exclusive",
|
||||
|
||||
child: Widget.CenterBox({
|
||||
className: "bar",
|
||||
|
||||
startWidget: Start(),
|
||||
centerWidget: Center(),
|
||||
endWidget: End(),
|
||||
}),
|
||||
});
|
||||
6
home/core/gui/ags/windows/bar/modules/battery.js
Normal file
6
home/core/gui/ags/windows/bar/modules/battery.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Battery, Widget } from "../../../imports.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Icon({ className: "battery module" })
|
||||
.bind("icon", Battery, "icon-name")
|
||||
.bind("tooltip-text", Battery, "percent", (p) => `Battery on ${p}%`);
|
||||
10
home/core/gui/ags/windows/bar/modules/bluetooth.js
Normal file
10
home/core/gui/ags/windows/bar/modules/bluetooth.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Bluetooth, Widget } from "../../../imports.js";
|
||||
import {
|
||||
getBluetoothIcon,
|
||||
getBluetoothText,
|
||||
} from "../../../utils/bluetooth.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Icon({ className: "bluetooth module" })
|
||||
.bind("icon", Bluetooth, "connected-devices", getBluetoothIcon)
|
||||
.bind("tooltip-text", Bluetooth, "connected-devices", getBluetoothText);
|
||||
70
home/core/gui/ags/windows/bar/modules/cpu_ram.js
Normal file
70
home/core/gui/ags/windows/bar/modules/cpu_ram.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Utils, Widget } from "../../../imports.js";
|
||||
|
||||
const Indicator = (props) =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
vpack: "center",
|
||||
|
||||
children: [
|
||||
Widget.Label({
|
||||
className: "type",
|
||||
label: props.type,
|
||||
}),
|
||||
Widget.Label({ className: "value" }).poll(2000, props.poll),
|
||||
],
|
||||
}).poll(2000, props.boxpoll);
|
||||
|
||||
const cpu = {
|
||||
type: "CPU",
|
||||
|
||||
poll: (self) =>
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
`top -bn1 | rg '%Cpu' | tail -1 | awk '{print 100-$8}'`,
|
||||
])
|
||||
.then((r) => (self.label = Math.round(Number(r)) + "%"))
|
||||
.catch((err) => print(err)),
|
||||
|
||||
boxpoll: (self) =>
|
||||
Utils.execAsync(["sh", "-c", "lscpu --parse=MHZ"])
|
||||
.then((r) => {
|
||||
const mhz = r.split("\n").slice(4);
|
||||
const freq = mhz.reduce((acc, e) => acc + Number(e), 0) / mhz.length;
|
||||
self.tooltipText = Math.round(freq) + " MHz";
|
||||
})
|
||||
.catch((err) => print(err)),
|
||||
};
|
||||
|
||||
const ram = {
|
||||
type: "MEM",
|
||||
poll: (self) =>
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
`free | tail -2 | head -1 | awk '{print $3/$2*100}'`,
|
||||
])
|
||||
.then((r) => (self.label = Math.round(Number(r)) + "%"))
|
||||
.catch((err) => print(err)),
|
||||
|
||||
boxpoll: (self) =>
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
"free --si -h | tail -2 | head -1 | awk '{print $3}'",
|
||||
])
|
||||
.then((r) => (self.tooltipText = r))
|
||||
.catch((err) => print(err)),
|
||||
};
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
onPrimaryClick: () => Utils.execAsync(["missioncenter"]),
|
||||
|
||||
child: Widget.Box({
|
||||
className: "system-info module",
|
||||
|
||||
children: [Indicator(cpu), Indicator(ram)],
|
||||
}),
|
||||
});
|
||||
10
home/core/gui/ags/windows/bar/modules/date.js
Normal file
10
home/core/gui/ags/windows/bar/modules/date.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Utils, Widget } from "../../../imports.js";
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
child: Widget.Label({ className: "date module" }).poll(1000, (self) =>
|
||||
Utils.execAsync(["date", "+%a %b %d %H:%M"]).then(
|
||||
(r) => (self.label = r),
|
||||
),
|
||||
),
|
||||
});
|
||||
40
home/core/gui/ags/windows/bar/modules/music.js
Normal file
40
home/core/gui/ags/windows/bar/modules/music.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Mpris, Widget } from "../../../imports.js";
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { findPlayer } from "../../../utils/mpris.js";
|
||||
|
||||
const Cover = (player) =>
|
||||
Widget.Box({ className: "cover" }).bind(
|
||||
"css",
|
||||
player,
|
||||
"cover-path",
|
||||
(cover) => `background-image: url('${cover ?? ""}');`,
|
||||
);
|
||||
|
||||
const Title = (player) =>
|
||||
Widget.Label({ className: "title module" }).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-title",
|
||||
(title) => ((title ?? "") == "Unknown title" ? "" : title),
|
||||
);
|
||||
|
||||
export const MusicBox = (player) =>
|
||||
Widget.Box({
|
||||
children: [Cover(player), Title(player)],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
className: "music",
|
||||
onPrimaryClick: () => App.toggleWindow("music"),
|
||||
})
|
||||
.hook(App, (self, window, visible) => {
|
||||
if (window === "music") {
|
||||
self.toggleClassName("active", visible);
|
||||
}
|
||||
})
|
||||
.bind("visible", Mpris, "players", (p) => p.length > 0)
|
||||
.bind("child", Mpris, "players", (players) => {
|
||||
if (players.length == 0) return Widget.Box();
|
||||
return MusicBox(findPlayer(players));
|
||||
});
|
||||
7
home/core/gui/ags/windows/bar/modules/net.js
Normal file
7
home/core/gui/ags/windows/bar/modules/net.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Network, Widget } from "../../../imports.js";
|
||||
import { getNetIcon, getNetText } from "../../../utils/net.js";
|
||||
|
||||
export default () =>
|
||||
Widget.Icon({ className: "net module" })
|
||||
.bind("icon", Network, "connectivity", getNetIcon)
|
||||
.bind("tooltip-text", Network, "connectivity", getNetText);
|
||||
47
home/core/gui/ags/windows/bar/modules/tray.js
Normal file
47
home/core/gui/ags/windows/bar/modules/tray.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { SystemTray, Widget } from "../../../imports.js";
|
||||
import Gdk from "gi://Gdk?version=3.0";
|
||||
|
||||
const Item = (item) =>
|
||||
Widget.Button({
|
||||
child: Widget.Icon().bind("icon", item, "icon"),
|
||||
|
||||
onPrimaryClick: (_, ev) => {
|
||||
try {
|
||||
item.activate(ev);
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
},
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
self.bind("tooltip-markup", item, "tooltip-markup");
|
||||
},
|
||||
|
||||
onSecondaryClick: (btn) =>
|
||||
item.menu?.popup_at_widget(
|
||||
btn,
|
||||
Gdk.Gravity.SOUTH,
|
||||
Gdk.Gravity.NORTH,
|
||||
null,
|
||||
),
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Box({ className: "tray module" }).bind(
|
||||
"children",
|
||||
SystemTray,
|
||||
"items",
|
||||
(items) => items.map(Item),
|
||||
);
|
||||
72
home/core/gui/ags/windows/bar/modules/workspaces.js
Normal file
72
home/core/gui/ags/windows/bar/modules/workspaces.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Hyprland, Widget } from "../../../imports.js";
|
||||
import {
|
||||
added,
|
||||
changeWorkspace,
|
||||
DEFAULT_MONITOR,
|
||||
focusedSwitch,
|
||||
getLastWorkspaceId,
|
||||
moveWorkspace,
|
||||
removed,
|
||||
workspaceActive,
|
||||
} from "../../../utils/hyprland.js";
|
||||
|
||||
globalThis.hyprland = Hyprland;
|
||||
|
||||
const makeWorkspaces = () =>
|
||||
[...Array(10)].map((_, i) => {
|
||||
const id = i + 1;
|
||||
|
||||
return Widget.Button({
|
||||
onPrimaryClick: () => changeWorkspace(id),
|
||||
|
||||
visible: getLastWorkspaceId() >= id,
|
||||
|
||||
setup: (self) => {
|
||||
const ws = Hyprland.getWorkspace(id);
|
||||
self.id = id;
|
||||
self.active = workspaceActive(id);
|
||||
self.monitor = DEFAULT_MONITOR;
|
||||
|
||||
if (self.active) {
|
||||
self.monitor = {
|
||||
name: ws?.monitor ?? DEFAULT_MONITOR.name,
|
||||
id: ws?.monitorID ?? DEFAULT_MONITOR.id,
|
||||
};
|
||||
self.toggleClassName(`monitor${self.monitor.id}`, true);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.EventBox({
|
||||
onScrollUp: () => changeWorkspace("+1"),
|
||||
onScrollDown: () => changeWorkspace("-1"),
|
||||
|
||||
child: Widget.Box({
|
||||
className: "workspaces module",
|
||||
|
||||
// The Hyprland service is ready later than ags is done parsing the config,
|
||||
// so only build the widget when we receive a signal from it.
|
||||
setup: (self) => {
|
||||
const connID = Hyprland.connect("notify::workspaces", () => {
|
||||
Hyprland.disconnect(connID);
|
||||
|
||||
self.children = makeWorkspaces();
|
||||
self.lastFocused = Hyprland.active.workspace.id;
|
||||
self.biggestId = getLastWorkspaceId();
|
||||
self
|
||||
.hook(Hyprland.active.workspace, focusedSwitch)
|
||||
.hook(Hyprland, added, "workspace-added")
|
||||
.hook(Hyprland, removed, "workspace-removed")
|
||||
.hook(
|
||||
Hyprland,
|
||||
(self, name, data) => {
|
||||
if (name === "moveworkspace") moveWorkspace(self, data);
|
||||
},
|
||||
"event",
|
||||
);
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
29
home/core/gui/ags/windows/music/controls.js
vendored
Normal file
29
home/core/gui/ags/windows/music/controls.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Icons, Widget } from "../../imports.js";
|
||||
import { mprisStateIcon } from "../../utils/mpris.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.CenterBox({
|
||||
className: "controls",
|
||||
hpack: "center",
|
||||
|
||||
startWidget: Widget.Button({
|
||||
onClicked: () => player.previous(),
|
||||
child: Widget.Icon(Icons.media.previous),
|
||||
}),
|
||||
|
||||
centerWidget: Widget.Button({
|
||||
onClicked: () => player.playPause(),
|
||||
|
||||
child: Widget.Icon().bind(
|
||||
"icon",
|
||||
player,
|
||||
"play-back-status",
|
||||
mprisStateIcon,
|
||||
),
|
||||
}),
|
||||
|
||||
endWidget: Widget.Button({
|
||||
onClicked: () => player.next(),
|
||||
child: Widget.Icon(Icons.media.next),
|
||||
}),
|
||||
});
|
||||
9
home/core/gui/ags/windows/music/cover.js
Normal file
9
home/core/gui/ags/windows/music/cover.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({ className: "cover" }).bind(
|
||||
"css",
|
||||
player,
|
||||
"cover-path",
|
||||
(cover) => `background-image: url('${cover ?? ""}')`,
|
||||
);
|
||||
43
home/core/gui/ags/windows/music/main.js
Normal file
43
home/core/gui/ags/windows/music/main.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Mpris, Widget } from "../../imports.js";
|
||||
import { findPlayer, generateBackground } from "../../utils/mpris.js";
|
||||
import PopupWindow from "../../utils/popup_window.js";
|
||||
|
||||
import Cover from "./cover.js";
|
||||
import { Artists, Title } from "./title_artists.js";
|
||||
import TimeInfo from "./time_info.js";
|
||||
import Controls from "./controls.js";
|
||||
import PlayerInfo from "./player_info.js";
|
||||
|
||||
const Info = (player) =>
|
||||
Widget.Box({
|
||||
className: "info",
|
||||
vertical: true,
|
||||
vexpand: false,
|
||||
hexpand: false,
|
||||
homogeneous: true,
|
||||
|
||||
children: [
|
||||
PlayerInfo(player),
|
||||
Title(player),
|
||||
Artists(player),
|
||||
Controls(player),
|
||||
TimeInfo(player),
|
||||
],
|
||||
});
|
||||
|
||||
const MusicBox = (player) =>
|
||||
Widget.Box({
|
||||
className: "music window",
|
||||
children: [Cover(player), Info(player)],
|
||||
}).bind("css", player, "cover-path", generateBackground);
|
||||
|
||||
export default () =>
|
||||
PopupWindow({
|
||||
monitor: 0,
|
||||
anchor: ["top"],
|
||||
name: "music",
|
||||
child: Widget.Box(),
|
||||
}).bind("child", Mpris, "players", (players) => {
|
||||
if (players.length == 0) return Widget.Box();
|
||||
return MusicBox(findPlayer(players));
|
||||
});
|
||||
21
home/core/gui/ags/windows/music/player_info.js
Normal file
21
home/core/gui/ags/windows/music/player_info.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Icons, Utils, Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
className: "player-info",
|
||||
vexpand: true,
|
||||
vpack: "start",
|
||||
|
||||
children: [
|
||||
Widget.Icon({
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
className: "player-icon",
|
||||
tooltipText: player.identity ?? "",
|
||||
}).bind("icon", player, "entry", (entry) => {
|
||||
// the Spotify icon is called spotify-client
|
||||
if (entry == "spotify") entry = "spotify-client";
|
||||
return Utils.lookUpIcon(entry ?? "") ? entry : Icons.media.player;
|
||||
}),
|
||||
],
|
||||
});
|
||||
68
home/core/gui/ags/windows/music/time_info.js
Normal file
68
home/core/gui/ags/windows/music/time_info.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
import { lengthStr } from "../../utils/mpris.js";
|
||||
|
||||
export const PositionLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "position",
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
|
||||
setup: (self) => {
|
||||
const update = (_, time) => {
|
||||
player.length > 0
|
||||
? (self.label = lengthStr(time || player.position))
|
||||
: (self.visible = !!player);
|
||||
};
|
||||
|
||||
self.hook(player, update, "position").poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export const LengthLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "length",
|
||||
hexpand: true,
|
||||
xalign: 1,
|
||||
})
|
||||
.bind("visible", player, "length", (length) => length > 0)
|
||||
.bind("label", player, "length", (length) => lengthStr(length));
|
||||
|
||||
export const Position = (player) =>
|
||||
Widget.Slider({
|
||||
className: "position",
|
||||
draw_value: false,
|
||||
|
||||
onChange: ({ value }) => (player.position = player.length * value),
|
||||
|
||||
setup: (self) => {
|
||||
const update = () => {
|
||||
if (self.dragging) return;
|
||||
|
||||
self.visible = player.length > 0;
|
||||
|
||||
if (player.length > 0) {
|
||||
self.value = player.position / player.length;
|
||||
}
|
||||
};
|
||||
|
||||
self
|
||||
.hook(player, update)
|
||||
.hook(player, update, "position")
|
||||
.poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
vpack: "end",
|
||||
|
||||
children: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
children: [PositionLabel(player), LengthLabel(player)],
|
||||
}),
|
||||
Position(player),
|
||||
],
|
||||
});
|
||||
32
home/core/gui/ags/windows/music/title_artists.js
Normal file
32
home/core/gui/ags/windows/music/title_artists.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
|
||||
export const Title = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "title",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({
|
||||
className: "title",
|
||||
label: "Nothing playing",
|
||||
}).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-title",
|
||||
(title) => title ?? "Nothing playing",
|
||||
),
|
||||
});
|
||||
|
||||
export const Artists = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "artists",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({ className: "artists" }).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-artists",
|
||||
(artists) => artists.join(", ") ?? "",
|
||||
),
|
||||
});
|
||||
139
home/core/gui/ags/windows/notifications/popups.js
Normal file
139
home/core/gui/ags/windows/notifications/popups.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Hyprland, Notifications, Utils, Widget } from "../../imports.js";
|
||||
|
||||
const closeAll = () => {
|
||||
Notifications.popups.map((n) => n.dismiss());
|
||||
};
|
||||
|
||||
/** @param {import("types/service/notifications").Notification} n */
|
||||
const NotificationIcon = ({ app_entry, app_icon, image }) => {
|
||||
if (image) {
|
||||
return Widget.Box({
|
||||
css: `
|
||||
background-image: url("${image}");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
if (Utils.lookUpIcon(app_icon)) {
|
||||
return Widget.Icon(app_icon);
|
||||
}
|
||||
|
||||
if (app_entry && Utils.lookUpIcon(app_entry)) {
|
||||
return Widget.Icon(app_entry);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/** @param {import('types/service/notifications').Notification} n */
|
||||
export const Notification = (n) => {
|
||||
const icon = Widget.Box({
|
||||
vpack: "start",
|
||||
class_name: "icon",
|
||||
// @ts-ignore
|
||||
setup: (self) => {
|
||||
let icon = NotificationIcon(n);
|
||||
if (icon !== null) {
|
||||
self.child = icon;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const title = Widget.Label({
|
||||
class_name: "title",
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
hexpand: true,
|
||||
max_width_chars: 24,
|
||||
truncate: "end",
|
||||
wrap: true,
|
||||
label: n.summary,
|
||||
use_markup: true,
|
||||
});
|
||||
|
||||
const body = Widget.Label({
|
||||
class_name: "body",
|
||||
hexpand: true,
|
||||
use_markup: true,
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
max_width_chars: 100,
|
||||
wrap: true,
|
||||
label: n.body,
|
||||
});
|
||||
|
||||
const actions = Widget.Box({
|
||||
class_name: "actions",
|
||||
children: n.actions
|
||||
.filter(({ id }) => id != "default")
|
||||
.map(({ id, label }) =>
|
||||
Widget.Button({
|
||||
class_name: "action-button",
|
||||
on_clicked: () => n.invoke(id),
|
||||
hexpand: true,
|
||||
child: Widget.Label(label),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return Widget.EventBox({
|
||||
on_primary_click: () => {
|
||||
if (n.actions.length > 0) n.invoke(n.actions[0].id);
|
||||
},
|
||||
on_middle_click: closeAll,
|
||||
on_secondary_click: () => n.dismiss(),
|
||||
child: Widget.Box({
|
||||
class_name: `notification ${n.urgency}`,
|
||||
vertical: true,
|
||||
|
||||
children: [
|
||||
Widget.Box({
|
||||
class_name: "info",
|
||||
children: [
|
||||
icon,
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
class_name: "text",
|
||||
vpack: "center",
|
||||
|
||||
setup: (self) => {
|
||||
if (n.body.length > 0) {
|
||||
self.children = [title, body];
|
||||
} else {
|
||||
self.children = [title];
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
actions,
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
let lastMonitor;
|
||||
export const notificationPopup = () =>
|
||||
Widget.Window({
|
||||
name: "notifications",
|
||||
anchor: ["top", "right"],
|
||||
child: Widget.Box({
|
||||
css: "padding: 1px;",
|
||||
class_name: "notifications",
|
||||
vertical: true,
|
||||
// @ts-ignore
|
||||
children: Notifications.bind("popups").transform((popups) => {
|
||||
return popups.map(Notification);
|
||||
}),
|
||||
}),
|
||||
}).hook(Hyprland.active, (self) => {
|
||||
// prevent useless resets
|
||||
if (lastMonitor === Hyprland.active.monitor) return;
|
||||
|
||||
self.monitor = Hyprland.active.monitor.id;
|
||||
});
|
||||
|
||||
export default notificationPopup;
|
||||
86
home/core/gui/ags/windows/osd/main.js
Normal file
86
home/core/gui/ags/windows/osd/main.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { Audio, Hyprland, Widget } from "../../imports.js";
|
||||
|
||||
import Brightness from "../../services/brightness.js";
|
||||
import Indicators from "../../services/osd.js";
|
||||
import PopupWindow from "../../utils/popup_window.js";
|
||||
|
||||
// connections
|
||||
Audio.connect("speaker-changed", () =>
|
||||
Audio.speaker.connect("changed", () => {
|
||||
if (!App.getWindow("system-menu")?.visible) {
|
||||
Indicators.speaker();
|
||||
}
|
||||
}),
|
||||
);
|
||||
Audio.connect("microphone-changed", () =>
|
||||
Audio.microphone.connect("changed", () => Indicators.mic()),
|
||||
);
|
||||
|
||||
Brightness.connect("screen-changed", () => {
|
||||
if (!App.getWindow("system-menu")?.visible) {
|
||||
Indicators.display();
|
||||
}
|
||||
});
|
||||
|
||||
let lastMonitor;
|
||||
|
||||
const child = () =>
|
||||
Widget.Box({
|
||||
className: "osd",
|
||||
|
||||
children: [
|
||||
Widget.Overlay({
|
||||
hexpand: true,
|
||||
visible: false,
|
||||
passThrough: true,
|
||||
|
||||
child: Widget.ProgressBar({
|
||||
hexpand: true,
|
||||
vertical: false,
|
||||
}).hook(Indicators, (self, props) => {
|
||||
self.value = props?.value ?? 0;
|
||||
self.visible = props?.showProgress ?? false;
|
||||
}),
|
||||
|
||||
overlays: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
|
||||
children: [
|
||||
Widget.Icon().hook(
|
||||
Indicators,
|
||||
(self, props) => (self.icon = props?.icon ?? ""),
|
||||
),
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
PopupWindow({
|
||||
name: "osd",
|
||||
monitor: 0,
|
||||
layer: "overlay",
|
||||
child: child(),
|
||||
click_through: true,
|
||||
anchor: ["bottom"],
|
||||
revealerSetup: (self) =>
|
||||
self.hook(Indicators, (revealer, _, visible) => {
|
||||
revealer.reveal_child = visible;
|
||||
}),
|
||||
})
|
||||
.hook(Hyprland.active, (self) => {
|
||||
// prevent useless resets
|
||||
if (lastMonitor === Hyprland.active.monitor) return;
|
||||
|
||||
self.monitor = Hyprland.active.monitor.id;
|
||||
})
|
||||
.hook(Indicators, (win, _, visible) => {
|
||||
win.visible = visible;
|
||||
});
|
||||
52
home/core/gui/ags/windows/system-menu/battery_info.js
Normal file
52
home/core/gui/ags/windows/system-menu/battery_info.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { App, Battery, Icons, Utils, Widget } from "../../imports.js";
|
||||
import { batteryTime } from "../../utils/battery.js";
|
||||
|
||||
const batteryEnergy = () => {
|
||||
return Battery.energyRate > 0.1 ? `${Battery.energyRate.toFixed(1)} W ` : "";
|
||||
};
|
||||
|
||||
const BatteryIcon = () =>
|
||||
Widget.Icon()
|
||||
.bind("icon", Battery, "percent", () => Battery.iconName)
|
||||
.bind("tooltip-text", Battery, "energy-rate", batteryEnergy);
|
||||
|
||||
const BatteryPercent = () =>
|
||||
Widget.Label().bind("label", Battery, "percent", (percent) => `${percent}%`);
|
||||
|
||||
const BatteryTime = () =>
|
||||
Widget.Label({
|
||||
className: "time",
|
||||
vexpand: true,
|
||||
vpack: "center",
|
||||
})
|
||||
.bind("label", Battery, "charging", batteryTime)
|
||||
.bind("label", Battery, "energy-rate", batteryTime);
|
||||
|
||||
const BatteryBox = () =>
|
||||
Widget.Box({
|
||||
className: "battery-box",
|
||||
visible: Battery.available,
|
||||
|
||||
children: [BatteryIcon(), BatteryPercent(), BatteryTime()],
|
||||
});
|
||||
|
||||
const PowerButton = () =>
|
||||
Widget.Button({
|
||||
className: "button disabled",
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
|
||||
onPrimaryClick: () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.exec("wlogout");
|
||||
},
|
||||
|
||||
child: Widget.Icon(Icons.powerButton),
|
||||
});
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
className: "battery-info",
|
||||
|
||||
children: [BatteryBox(), PowerButton()],
|
||||
});
|
||||
22
home/core/gui/ags/windows/system-menu/main.js
Normal file
22
home/core/gui/ags/windows/system-menu/main.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Widget } from "../../imports.js";
|
||||
import PopupWindow from "../../utils/popup_window.js";
|
||||
|
||||
import Toggles from "./toggles.js";
|
||||
import Sliders from "./sliders.js";
|
||||
import BatteryInfo from "./battery_info.js";
|
||||
|
||||
const SystemMenuBox = () =>
|
||||
Widget.Box({
|
||||
className: "system-menu",
|
||||
vertical: true,
|
||||
|
||||
children: [Toggles(), Sliders(), BatteryInfo()],
|
||||
});
|
||||
|
||||
export default () =>
|
||||
PopupWindow({
|
||||
monitor: 0,
|
||||
anchor: ["top", "right"],
|
||||
name: "system-menu",
|
||||
child: SystemMenuBox(),
|
||||
});
|
||||
74
home/core/gui/ags/windows/system-menu/sliders.js
Normal file
74
home/core/gui/ags/windows/system-menu/sliders.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { App, Audio, Icons, Utils, Widget } from "../../imports.js";
|
||||
import Brightness from "../../services/brightness.js";
|
||||
import { audioIcon } from "../../utils/audio.js";
|
||||
|
||||
const Slider = (args) =>
|
||||
Widget.Box({
|
||||
...(args.props ?? {}),
|
||||
className: args.name,
|
||||
|
||||
children: [
|
||||
Widget.Button({
|
||||
onPrimaryClick: args.icon.action ?? null,
|
||||
child: Widget.Icon({
|
||||
icon: args.icon.icon ?? "",
|
||||
setup: args.icon.setup,
|
||||
}),
|
||||
}),
|
||||
Widget.Slider({
|
||||
drawValue: false,
|
||||
hexpand: true,
|
||||
setup: args.slider.setup,
|
||||
onChange: args.slider.onChange ?? null,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const vol = () => {
|
||||
return {
|
||||
name: "volume",
|
||||
icon: {
|
||||
icon: "",
|
||||
action: () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.execAsync("pwvucontrol");
|
||||
},
|
||||
setup: (self) =>
|
||||
self
|
||||
.bind("icon", Audio.speaker, "volume", audioIcon)
|
||||
.bind("icon", Audio.speaker.stream, "is-muted", audioIcon),
|
||||
},
|
||||
slider: {
|
||||
setup: (self) => self.bind("value", Audio.speaker, "volume"),
|
||||
onChange: ({ value }) => (Audio.speaker.volume = value),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const brightness = () => {
|
||||
return {
|
||||
name: "brightness",
|
||||
icon: {
|
||||
icon: Icons.brightness,
|
||||
},
|
||||
slider: {
|
||||
setup: (self) => self.bind("value", Brightness, "screen-value"),
|
||||
onChange: ({ value }) => (Brightness.screenValue = value),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
className: "sliders",
|
||||
vertical: true,
|
||||
|
||||
// The Audio service is ready later than ags is done parsing the config,
|
||||
// so only build the widget when we receive a signal from it.
|
||||
setup: (self) => {
|
||||
const connID = Audio.connect("notify::speaker", () => {
|
||||
Audio.disconnect(connID);
|
||||
self.children = [Slider(vol()), Slider(brightness())];
|
||||
});
|
||||
},
|
||||
});
|
||||
102
home/core/gui/ags/windows/system-menu/toggles.js
Normal file
102
home/core/gui/ags/windows/system-menu/toggles.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { App, Bluetooth, Network, Utils, Widget } from "../../imports.js";
|
||||
|
||||
import { getNetIcon, getNetText } from "../../utils/net.js";
|
||||
import { getBluetoothIcon, getBluetoothText } from "../../utils/bluetooth.js";
|
||||
|
||||
const Toggle = (args) =>
|
||||
Widget.Box({
|
||||
...(args.props ?? {}),
|
||||
className: `toggle ${args.name}`,
|
||||
hexpand: true,
|
||||
hpack: "start",
|
||||
|
||||
children: [
|
||||
Widget.Button({
|
||||
className: "button",
|
||||
|
||||
child: Widget.Icon({
|
||||
setup: args.icon.setup,
|
||||
}),
|
||||
setup: args.icon.buttonSetup,
|
||||
}),
|
||||
Widget.Button({
|
||||
hexpand: true,
|
||||
child: Widget.Label({
|
||||
hpack: "start",
|
||||
setup: args.label.setup,
|
||||
}),
|
||||
setup: args.label.buttonSetup,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const net = {
|
||||
name: "net",
|
||||
icon: {
|
||||
setup: (self) =>
|
||||
self
|
||||
.bind("icon", Network, "connectivity", getNetIcon)
|
||||
.bind("icon", Network.wifi, "icon-name", getNetIcon),
|
||||
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => Network.toggleWifi();
|
||||
self.hook(
|
||||
Network,
|
||||
(btn) =>
|
||||
btn.toggleClassName("disabled", Network.connectivity != "full"),
|
||||
"notify::connectivity",
|
||||
);
|
||||
},
|
||||
},
|
||||
label: {
|
||||
setup: (self) =>
|
||||
self
|
||||
.bind("label", Network, "connectivity", () => getNetText())
|
||||
.bind("label", Network.wifi, "ssid", () => getNetText()),
|
||||
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.execAsync([
|
||||
"sh",
|
||||
"-c",
|
||||
"XDG_CURRENT_DESKTOP=GNOME gnome-control-center",
|
||||
]);
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const bt = {
|
||||
name: "bluetooth",
|
||||
icon: {
|
||||
setup: (self) =>
|
||||
self.bind("icon", Bluetooth, "connected-devices", getBluetoothIcon),
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => Bluetooth.toggle();
|
||||
self.hook(
|
||||
Bluetooth,
|
||||
(btn) => btn.toggleClassName("disabled", !Bluetooth.enabled),
|
||||
"notify::enabled",
|
||||
);
|
||||
},
|
||||
},
|
||||
label: {
|
||||
setup: (self) =>
|
||||
self.bind("label", Bluetooth, "connected-devices", getBluetoothText),
|
||||
buttonSetup: (self) => {
|
||||
self.onPrimaryClick = () => {
|
||||
App.toggleWindow("system-menu");
|
||||
Utils.execAsync("overskride");
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default () =>
|
||||
Widget.Box({
|
||||
className: "toggles",
|
||||
vertical: true,
|
||||
|
||||
children: [Toggle(net), Toggle(bt)],
|
||||
});
|
||||
Reference in New Issue
Block a user