added stuff

This commit is contained in:
Charlie Root 2024-04-09 23:11:33 +02:00
commit 9d0ebdfbd0
907 changed files with 70990 additions and 0 deletions

View file

@ -0,0 +1,71 @@
import { Widget } from "../../imports.js";
const { Window, Box, CenterBox } = Widget;
// Widgets
import { LauncherIcon } from "./modules/launcher.js";
import { Workspaces } from "./modules/workspaces.js";
import { Tray } from "./modules/tray.js";
import { BatteryWidget } from "./modules/battery.js";
import { Clock } from "./modules/clock.js";
import { PowerMenu } from "./modules/power.js";
import { Swallow } from "./modules/swallow.js";
import { BluetoothWidget } from "./modules/bluetooth.js";
import { AudioWidget } from "./modules/audio.js";
import { NetworkWidget } from "./modules/network.js";
import { SystemUsage } from "./modules/system.js";
import { Weather } from "./modules/weather.js";
const Top = () =>
Box({
className: "barTop",
vertical: true,
vpack: "start",
children: [LauncherIcon(), SystemUsage(), Weather()],
});
const Center = () =>
Box({
className: "barCenter",
vertical: true,
children: [Workspaces()],
});
const Bottom = () =>
Box({
className: "barBottom",
vertical: true,
vpack: "end",
children: [
Tray(),
Box({
className: "utilsBox",
vertical: true,
children: [
BluetoothWidget(),
AudioWidget(),
Swallow(),
BatteryWidget(),
NetworkWidget(),
],
}),
Clock(),
PowerMenu(),
],
});
export const Bar = ({ monitor } = {}) =>
Window({
name: "bar",
anchor: ["top", "bottom", "left"],
exclusivity: "exclusive",
layer: "top",
margins: [8, 0, 8, 8],
monitor,
child: CenterBox({
className: "bar",
vertical: true,
startWidget: Top(),
centerWidget: Center(),
endWidget: Bottom(),
}),
});

View file

@ -0,0 +1,22 @@
import { Audio, Widget } from "../../../imports.js";
import { getAudioIcon } from "../../../utils/audio.js";
import { launchApp } from "../../../utils/global.js";
const { Button, Icon } = Widget;
const AudioIcon = () =>
Icon({
setup: (self) => {
self.hook(Audio, getAudioIcon, "speaker-changed");
},
});
export const AudioWidget = () => {
return Button({
className: "audio",
cursor: "pointer",
visible: true,
child: AudioIcon(),
onClicked: () => launchApp("pavucontrol"),
});
};

View file

@ -0,0 +1,35 @@
import { Widget, Battery } from "../../../imports.js";
import {
getBatteryPercentage,
getBatteryTooltip,
getBatteryIcon,
} from "../../../utils/battery.js";
const { Button, Box, Label, Revealer } = Widget;
const BatIcon = () =>
Label({ className: "batIcon" })
// NOTE: label needs to be used instead of icon here
.bind("label", Battery, "percent", getBatteryIcon)
.bind("tooltip-text", Battery, "percent", getBatteryTooltip);
const BatStatus = () =>
Revealer({
transition: "slide_down",
transition_duration: 200,
child: Label().bind("label", Battery, "percent", getBatteryPercentage),
});
export const BatteryWidget = () =>
Button({
onPrimaryClick: (self) => {
self.child.children[1].revealChild =
!self.child.children[1].revealChild;
},
child: Box({
className: "battery",
cursor: "pointer",
vertical: true,
children: [BatIcon(), BatStatus()],
visible: Battery.bind("available"),
}),
});

View file

@ -0,0 +1,29 @@
import { Bluetooth, Widget, Utils } from "../../../imports.js";
import {
getBluetoothIcon,
getBluetoothLabel,
getBluetoothClass,
getBluetoothTooltip,
} from "../../../utils/bluetooth.js";
const { Button, Label } = Widget;
const BluetoothModule = () =>
Label({ className: "bluetoothIcon" })
.bind("label", Bluetooth, "connected-devices", getBluetoothIcon)
.bind("class", Bluetooth, "connected-devices", getBluetoothClass)
.bind("label", Bluetooth, "connected-devices", getBluetoothLabel)
.bind(
"tooltip-text",
Bluetooth,
"connected-devices",
getBluetoothTooltip,
);
export const BluetoothWidget = () =>
Button({
className: "bluetooth",
cursor: "pointer",
child: BluetoothModule(),
visible: Bluetooth.connectedDevices.length > 0,
onClicked: () => Utils.exec("blueman-applet"),
});

View file

@ -0,0 +1,26 @@
import { Widget, Utils } from "../../../imports.js";
const { exec, execAsync } = Utils;
const { Label, Box } = Widget;
const Time = () =>
Label({
className: "timeLabel",
setup: (self) => {
// the current quote syntax is the only one that works
// eslint-disable-next-line quotes
self.poll(1000, (self) => (self.label = exec('date "+%H%n%M"')));
self.poll(1000, (self) =>
execAsync(["date", "+%H%n%M"])
.then((time) => (self.label = time))
// eslint-disable-next-line no-undef
.catch(print.error),
);
},
});
export const Clock = () =>
Box({
className: "clock",
vertical: true,
children: [Time()],
});

View file

@ -0,0 +1,15 @@
import { Widget, App } from "../../../imports.js";
import { getLauncherIcon } from "../../../utils/launcher.js";
const { Button, Label } = Widget;
export const LauncherIcon = () =>
Button({
vexpand: false,
className: "launcherIcon",
cursor: "pointer",
child: Label("󱢦"),
onClicked: () => App.toggleWindow("launcher"),
setup: (self) => {
self.hook(App, getLauncherIcon, "window-toggled");
},
});

View file

@ -0,0 +1,10 @@
import { Widget, Utils } from "../../../imports.js";
const { Button, Label } = Widget;
export const Lock = () =>
Button({
className: "lock",
cursor: "pointer",
child: Label(""),
onClicked: () => Utils.exec("swaylock"),
});

View file

@ -0,0 +1,34 @@
import { Network, Widget, Utils } from "../../../imports.js";
import {
getWifiIcon,
getWifiTooltip,
getWiredIcon,
getWiredTooltip,
} from "../../../utils/network.js";
const { Stack, Button, Label } = Widget;
const WifiIndicator = () =>
Label({ has_tooltip: true })
.bind("label", Network.wifi, "strength", getWifiIcon)
.bind("tooltip-text", Network.wifi, "strength", getWifiTooltip);
const WiredIndicator = () =>
Label({ cursor: "pointer" })
.bind("label", Network.wired, "internet", getWiredIcon)
.bind("tooltip-text", Network.wired, "internet", getWiredTooltip);
export const NetworkWidget = () =>
Button({
className: "network",
cursor: "pointer",
onClicked: () => Utils.exec("nm-connection-editor"),
child: Stack({
shown: Network.bind("primary").as(
(/** @type {any} */ p) => p || "wifi",
),
children: {
wifi: WifiIndicator(),
wired: WiredIndicator(),
},
}),
});

View file

@ -0,0 +1,10 @@
import { Widget } from "../../../imports.js";
const { Button, Label } = Widget;
export const PowerMenu = () =>
Button({
vexpand: false,
className: "power",
cursor: "pointer",
child: Label(""),
});

View file

@ -0,0 +1,15 @@
import { Widget } from "../../../imports.js";
const { Label, Button } = Widget;
import { toggleSwallowStatus, status } from "../../../utils/swallow.js";
export const Swallow = () =>
Button({
className: "swallow",
cursor: "pointer",
tooltipText: `Swallow: ${status.value}`,
onPrimaryClick: toggleSwallowStatus,
child: Label({
label: "󰊰",
}),
}).hook(status, (self) => (self.tooltipText = `${status.value}`));

View file

@ -0,0 +1,134 @@
import { Variable, Widget } from "../../../imports.js";
const { Button, Revealer, Box, Label, CircularProgress } = Widget;
const getMemClass = (v) => {
const val = v * 100;
const className = [
[100, "memCritical"],
[75, "memHigh"],
[35, "memMod"],
[5, "memLow"],
[0, "memIdle"],
[-1, "memRevealer"],
].find(([threshold]) => threshold <= val)[1];
return className;
};
const getCpuClass = (v) => {
const val = v * 100;
const className = [
[100, "cpuCritical"],
[75, "cpuHigh"],
[35, "cpuMod"],
[5, "cpuLow"],
[0, "cpuIdle"],
[-1, "cpuRevealer"],
].find(([threshold]) => threshold <= val)[1];
return className;
};
const divide = ([total, free]) => free / total;
const cpu = Variable(0, {
poll: [
2000,
"top -b -n 1",
(out) =>
divide([
100,
out
.split("\n")
.find((line) => line.includes("Cpu(s)"))
.split(/\s+/)[1]
.replace(",", "."),
]),
],
});
const mem = Variable(0, {
poll: [
2000,
"free",
(out) =>
divide(
out
.split("\n")
.find((line) => line.includes("Mem:"))
.split(/\s+/)
.splice(1, 2),
),
],
});
/**
* @param {string} name
* @param {typeof cpu | typeof ram} process
* @param {Array<any>} extraChildren
* @param {() => void} onPrimary
*/
const systemWidget = (name, process, extraChildren = [], onPrimary) =>
Button({
className: name + "Button",
onPrimaryClick: onPrimary,
child: Box({
className: name,
vertical: true,
children: [
CircularProgress({
className: name + "Progress",
// binds: [["value", process]],
rounded: true,
inverted: false,
startAt: 0.27,
}).bind("value", process),
...extraChildren,
],
}),
});
const CPU = systemWidget(
"cpu",
cpu,
[
Revealer({
transition: "slide_down",
child: Label()
.bind("label", cpu, "value", (v) => `${Math.floor(v * 100)}%`)
.bind("className", cpu, "value", getCpuClass),
transition_duration: 250,
}),
],
(self) => {
self.child.children[1].revealChild =
!self.child.children[1].revealChild;
},
);
const MEM = systemWidget(
"mem",
mem,
[
Revealer({
transition: "slide_down",
child: Label()
.bind("label", mem, "value", (v) => `${Math.floor(v * 100)}%`)
.bind("className", cpu, "value", getMemClass),
transition_duration: 250,
}),
],
(self) => {
self.child.children[1].revealChild =
!self.child.children[1].revealChild;
},
);
export const SystemUsage = () =>
Box({
className: "systemUsage",
vertical: true,
cursor: "pointer",
children: [CPU, MEM],
});

View file

@ -0,0 +1,40 @@
import { Widget, SystemTray } from "../../../imports.js";
import { getTrayItems } from "../../../utils/tray.js";
const { Box, EventBox, Label, Revealer } = Widget;
const RevIcon = () =>
Label({
className: "trayChevron",
label: "",
});
const TrayItems = () =>
Box({
className: "trayIcons",
vertical: true,
setup: (self) => {
self.hook(SystemTray, getTrayItems);
},
});
export const Tray = () =>
EventBox({
onPrimaryClick: (self) => {
self.child.children[0].label = self.child.children[1].revealChild
? ""
: "";
self.child.children[1].revealChild =
!self.child.children[1].revealChild;
},
child: Box({
className: "tray",
vertical: true,
children: [
RevIcon(),
Revealer({
transition: "slide_up",
child: TrayItems(),
}),
],
}),
});

View file

@ -0,0 +1,24 @@
import { Widget } from "../../../imports.js";
import {
WeatherValue,
getWeatherIcon,
getWeatherTooltip,
} from "../../../utils/weather.js";
const { Label } = Widget;
const weatherWidget = () =>
Label({
hexpand: false,
vexpand: false,
class_name: "weather",
setup: (self) => {
self.bind("label", WeatherValue, "value", getWeatherIcon);
self.bind("tooltip-text", WeatherValue, "value", getWeatherTooltip);
},
});
export const Weather = () =>
Widget.CenterBox({
vertical: true,
centerWidget: weatherWidget(),
});

View file

@ -0,0 +1,25 @@
import { Widget, Hyprland } from "../../../imports.js";
import { getFocusedWorkspace } from "../../../utils/hyprland.js";
const { Box, Button } = Widget;
const { messageAsync } = Hyprland;
export const Workspaces = () =>
Box({
className: "workspaces",
child: Box({
vertical: true,
children: Array.from({ length: 10 }, (_, i) => i + 1).map((i) =>
Button({
cursor: "pointer",
attribute: { index: i },
onClicked: () => messageAsync(`dispatch workspace ${i}`),
onSecondaryClick: () =>
messageAsync(`dispatch movetoworkspacesilent ${i}`),
}),
),
setup: (self) => {
self.hook(Hyprland, getFocusedWorkspace);
},
}),
});

View file

@ -0,0 +1,11 @@
import { Widget } from "../../imports.js";
const { Box } = Widget;
export const DesktopIcons = () =>
Box({
className: "desktopIcons",
vertical: true,
hpack: "start",
vpack: "start",
children: [],
});

View file

@ -0,0 +1,98 @@
import { Widget, Utils } from "../../imports.js";
const { Box, EventBox, Label, MenuItem, Menu } = Widget;
const { exec, execAsync } = Utils;
/**
* Creates a menu item with an icon.
* @param {string} icon - The icon to display for the menu item.
* @param {string} itemLabel - The label for the menu item.
* @param {Function} onClick - The function to be executed when the menu item is activated.
* @returns {Object} A menu item object with the specified icon, label, and click action.
*/
function ItemWithIcon(icon, itemLabel, onClick) {
return MenuItem({
className: "desktopMenuItem",
child: Box({
children: [
Label({
className: "desktopMenuItemIcon",
label: icon,
}),
Label(itemLabel),
],
}),
onActivate: onClick,
});
}
const Separator = () =>
MenuItem({
child: Box({
className: "separator",
css: `
min-height: 1px;
margin: 3px 6px;
`,
}),
});
const rioMenu = () => {
return [
ItemWithIcon("󰆍", "Terminal", () =>
exec(
'sh -c "$HOME/.config/ags/bin/open_window `slurp -d -c 999999 -w 2` foot"',
),
),
ItemWithIcon("󰘖", "Resize", () =>
exec(
'sh -c "$HOME/.config/ags/bin/move_window `slurp -d -c 999999 -w 2`"',
),
),
ItemWithIcon("󰁁", "Move", () => exec("hyprctl dispatch submap move")),
ItemWithIcon("󰅖", "Delete", () => exec("hyprctl kill")),
Separator(),
];
};
const Powermenu = () => {
return MenuItem({
className: "desktopMenuItem",
child: Box({
children: [
Label({
className: "desktopMenuItemIcon",
label: "󰐥",
}),
Label("Powermenu"),
],
}),
submenu: Menu({
className: "desktopMenu",
children: [
ItemWithIcon("󰍁", "Lock", () => Utils.exec("gtklock")),
ItemWithIcon("󰍃", "Log Out", () =>
exec("hyprctl dispatch exit"),
),
ItemWithIcon("󰖔", "Suspend", () => exec("systemctl suspend")),
ItemWithIcon("󰜉", "Reboot", () => exec("systemctl reboot")),
ItemWithIcon("󰐥", "Shutdown", () => exec("systemctl poweroff")),
],
}),
});
};
export const DesktopMenu = () =>
EventBox({
onSecondaryClick: (_, event) =>
Menu({
className: "desktopMenu",
children: [
...rioMenu(),
ItemWithIcon("󰈊", "Colorpicker", () =>
execAsync(["hyprpicker", "-a", "wl-copy"]),
),
Separator(),
Powermenu(),
],
}).popup_at_pointer(event),
});

View file

@ -0,0 +1,17 @@
import { Widget } from "../../imports.js";
const { Window } = Widget;
import { DesktopMenu } from "./desktopMenu.js";
import { DesktopIcons } from "./desktopIcons.js";
export const Desktop = ({ monitor } = {}) =>
Window({
name: "desktop",
anchor: ["top", "bottom", "left", "right"],
layer: "bottom",
monitor,
child: Widget.Overlay({
child: DesktopMenu(),
overlays: [DesktopIcons()],
}),
});

View file

@ -0,0 +1,113 @@
import { Widget, App, Applications, Utils, Hyprland } from "../../imports.js";
import PopupWindow from "../../utils/popupWindow.js";
const { Box, Button, Icon, Label, Scrollable, Entry } = Widget;
const WINDOW_NAME = "launcher";
const truncateString = (str, maxLength) =>
str.length > maxLength ? `${str.slice(0, maxLength)}...` : str;
const AppItem = (app) =>
Button({
className: "launcherApp",
onClicked: () => {
App.closeWindow(WINDOW_NAME);
Hyprland.messageAsync(`dispatch exec gtk-launch ${app.desktop}`);
++app.frequency;
},
setup: (self) => (self.app = app),
child: Box({
children: [
Icon({
className: "launcherItemIcon",
icon: app.iconName || "",
size: 24,
}),
Box({
className: "launcherItem",
vertical: true,
vpack: "center",
children: [
Label({
className: "launcherItemTitle",
label: app.name,
xalign: 0,
vpack: "center",
truncate: "end",
}),
!!app.description &&
Widget.Label({
className: "launcherItemDescription",
label:
truncateString(app.description, 75) || "",
wrap: true,
xalign: 0,
justification: "left",
vpack: "center",
}),
],
}),
],
}),
});
const Launcher = () => {
const list = Box({ vertical: true });
const entry = Entry({
className: "launcherEntry",
hexpand: true,
text: "-",
onAccept: ({ text }) => {
const isCommand = text.startsWith(">");
const appList = Applications.query(text || "");
if (isCommand === true) {
App.toggleWindow(WINDOW_NAME);
Utils.execAsync(text.slice(1));
} else if (appList[0]) {
App.toggleWindow(WINDOW_NAME);
appList[0].launch();
}
},
onChange: ({ text }) =>
list.children.map((item) => {
item.visible = item.app.match(text);
}),
});
return Widget.Box({
className: "launcher",
vertical: true,
setup: (self) => {
self.hook(App, (_, name, visible) => {
if (name !== WINDOW_NAME) return;
list.children = Applications.list.map(AppItem);
entry.text = "";
if (visible) entry.grab_focus();
});
},
children: [
entry,
Scrollable({
hscroll: "never",
css: "min-width: 250px; min-height: 360px;",
child: list,
}),
],
});
};
export const AppLauncher = () =>
PopupWindow({
name: WINDOW_NAME,
anchor: ["top", "bottom", "right"],
margins: [13, 13, 0, 13],
layer: "overlay",
transition: "slide_down",
transitionDuration: 150,
popup: true,
keymode: "on-demand",
child: Launcher(),
});

View 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),
}),
});

View 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 ?? ""}')`,
);

View file

@ -0,0 +1,45 @@
import { Mpris, Widget } from "../../imports.js";
import { findPlayer, generateBackground } from "../../utils/mpris.js";
import PopupWindow from "./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 const Media = () =>
PopupWindow({
monitor: 0,
anchor: ["top"],
layer: "top",
margins: [8, 0, 0, 0],
name: "music",
child: Widget.Box(),
}).bind("child", Mpris, "players", (players) => {
if (players.length == 0) return Widget.Box();
return MusicBox(findPlayer(players));
});

View file

@ -0,0 +1,23 @@
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;
}),
],
});

View file

@ -0,0 +1,46 @@
import App from "resource:///com/github/Aylur/ags/app.js";
import { Widget } from "../../imports.js";
const { Box, Revealer, Window } = Widget;
export default ({
name,
child,
revealerSetup = null,
transition = "crossfade",
transitionDuration = 200,
...props
}) => {
const window = Window({
name,
popup: false,
focusable: false,
visible: false,
...props,
setup: (self) => (self.getChild = () => child),
child: Box({
css: `
min-height: 1px;
min-width: 1px;
padding: 1px;
`,
child: Revealer({
transition,
transitionDuration,
child: child,
setup:
revealerSetup ??
((self) =>
self.hook(App, (self, currentName, visible) => {
if (currentName === name) {
self.reveal_child = visible;
}
})),
}),
}),
});
return window;
};

View file

@ -0,0 +1,67 @@
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),
],
});

View 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(", ") ?? "",
),
});

View file

@ -0,0 +1,134 @@
import { Hyprland, Notifications, Utils, Widget } from "../../imports.js";
const { Box, Icon, Label, Button, EventBox, Window } = Widget;
const { lookUpIcon } = Utils;
const closeAll = () => {
Notifications.popups.map((n) => n.dismiss());
};
const NotificationIcon = ({ app_entry, app_icon, image }) => {
if (image) {
return Box({
css: `
background-image: url("${image}");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
`,
});
}
if (lookUpIcon(app_icon)) {
return Icon(app_icon);
}
if (app_entry && lookUpIcon(app_entry)) {
return Icon(app_entry);
}
return null;
};
const Notification = (notif) => {
const icon = Box({
vpack: "start",
class_name: "icon",
// @ts-ignore
setup: (/** @type {{ child: any; }} */ self) => {
const icon = NotificationIcon(notif);
if (icon !== null) self.child = icon;
},
});
const title = Label({
class_name: "title",
xalign: 0,
justification: "left",
hexpand: true,
max_width_chars: 24,
truncate: "end",
wrap: true,
label: notif.summary,
use_markup: true,
});
const body = Label({
class_name: "body",
hexpand: true,
use_markup: true,
xalign: 0,
justification: "left",
max_width_chars: 100,
wrap: true,
label: notif.body,
});
const actions = Box({
class_name: "actions",
children: notif.actions
.filter(({ id }) => id != "default")
.map(({ id, label }) =>
Button({
class_name: "action-button",
on_clicked: () => notif.invoke(id),
hexpand: true,
child: Label(label),
}),
),
});
return EventBox({
on_primary_click: () => {
if (notif.actions.length > 0) notif.invoke(notif.actions[0].id);
},
on_middle_click: closeAll,
on_secondary_click: () => notif.dismiss(),
child: Box({
class_name: `notification ${notif.urgency}`,
vertical: true,
children: [
Box({
class_name: "info",
children: [
icon,
Box({
vertical: true,
class_name: "text",
vpack: "center",
setup: (self) => {
if (notif.body.length > 0)
self.children = [title, body];
else self.children = [title];
},
}),
],
}),
actions,
],
}),
});
};
let lastMonitor;
export const Notifs = () =>
Window({
name: "notifications",
anchor: ["top", "right"],
margins: [8, 8, 8, 0],
child: 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;
});

View file

@ -0,0 +1,58 @@
import { Widget, Utils } from "../../imports.js";
import Brightness from "../../services/brightness.js";
const { Box, Slider, Label, Revealer } = Widget;
const BrightnessIcon = () =>
Label({
className: "brtPopupIcon",
setup: (self) => {
self.hook(Brightness, (self) => {
const icons = ["", "", "", "", "", "", "", "", ""];
let index = Math.floor((Brightness.screen * 100) / 11);
index = Math.max(0, Math.min(index, icons.length - 1));
if (index >= 0 && index < icons.length) {
self.label = icons[index].toString();
} else {
log("Index out of bounds:", index);
}
});
},
});
const PercentBar = () =>
Slider({
className: "brtPopupBar",
drawValue: false,
onChange: ({ value }) => (Brightness.screen = value),
setup: (self) => {
self.hook(Brightness, (self) => (self.value = Brightness.screen));
},
});
export const BrightnessPopup = () =>
Box({
css: `min-height: 1px;
min-width: 1px;`,
child: Revealer({
transition: "slide_up",
child: Box({
className: "brightnessPopup",
children: [BrightnessIcon(), PercentBar()],
}),
attribute: { count: 0 },
setup: (self) => {
self.hook(Brightness, (self) => {
self.revealChild = true;
self.attribute.count++;
Utils.timeout(1500, () => {
self.attribute.count--;
if (self.attribute.count === 0)
self.revealChild = false;
});
});
},
}),
});

View file

@ -0,0 +1,18 @@
import { Widget } from "../../imports.js";
// Widgets
import { BrightnessPopup } from "./brightnessPopup.js";
import { VolumePopup } from "./volumePopup.js";
export const Popups = () =>
Widget.Window({
name: "popups",
className: "popups",
anchor: ["bottom", "right"],
layer: "overlay",
margins: [0, 12, 8, 0],
child: Widget.Box({
vertical: true,
children: [BrightnessPopup(), VolumePopup()],
}),
});

View file

@ -0,0 +1,36 @@
import { Widget, Utils, Audio } from "../../imports.js";
import { getSliderIcon, volumePercentBar } from "../../utils/audio.js";
const { Box, Revealer } = Widget;
const { speaker } = Audio;
const { timeout } = Utils;
export const VolumePopup = () =>
Box({
css: `
min-height: 2px;
min-width: 2px;
`,
child: Revealer({
transition: "slide_up",
child: Box({
className: "volumePopup",
children: [getSliderIcon(), volumePercentBar()],
}),
attribute: { count: 0 },
setup: (self) =>
self.hook(
speaker,
() => {
self.reveal_child = true;
self.attribute.count++;
timeout(1500, () => {
self.attribute.count--;
if (self.attribute.count === 0)
self.reveal_child = false;
});
},
"notify::volume",
),
}),
});