Files
hyprland/config/ags/modules/bar.ts
T
2024-06-26 00:10:21 +05:45

452 lines
13 KiB
TypeScript

const hyprland = await Service.import("hyprland")
const battery = await Service.import("battery")
const systemtray = await Service.import("systemtray")
const audio = await Service.import("audio")
const network = await Service.import("network")
const { Gtk, GLib, Gio } = imports.gi;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
import { OpenSettings } from "apps/settings/main.ts";
import { enableClickThrough } from "./misc/clickthrough.js";
import { RoundedCorner } from "./misc/cairo_roundedcorner.js";
import { Client, Workspace } from "types/service/hyprland.js"
import Button from "types/widgets/button.js"
import Icon from "types/widgets/icon.js"
import { FileEnumerator, FileInfo } from "types/@girs/gio-2.0/gio-2.0.cjs"
const mpris = await Service.import("mpris")
function checkKeymap() {
const layout = Utils.execAsync(`hyprctl devices -j | jq -r '.keyboards[] | select(.main == true) | .active_keymap' | tail -n1 | cut -c1-2 | tr 'A-Z' 'a-z'`)
.then(out => { return out })
.catch(err => { print(err); return "en" });
return layout;
}
const keyboard_layout = Variable("en")
keyboard_layout.setValue(await checkKeymap())
hyprland.connect("keyboard-layout", (hyprland, keyboardname, layoutname) => {
keyboard_layout.setValue(layoutname.trim().toLowerCase().substr(0, 2))
})
const time = Variable("", {
poll: [1000, 'date "+%I:%M %p"'],
})
const date = Variable("", {
poll: [1000, 'date "+%Y-%m-%d"'],
})
function getIconNameFromClass(windowClass: string) {
let formattedClass = windowClass.replace(/\s+/g, '-').toLowerCase();
let homeDir = GLib.get_home_dir();
let systemDataDirs = GLib.get_system_data_dirs().map(dir => dir + '/applications');
let dataDirs = systemDataDirs.concat([homeDir + '/.local/share/applications']);
let icon: string | undefined;
for (let dir of dataDirs) {
let applicationsGFile = Gio.File.new_for_path(dir);
let enumerator: FileEnumerator;
try {
enumerator = applicationsGFile.enumerate_children('standard::name,standard::type', Gio.FileQueryInfoFlags.NONE, null);
} catch (e) {
continue;
}
let fileInfo: FileInfo | null;
while ((fileInfo = enumerator.next_file(null)) !== null) {
let desktopFile = fileInfo.get_name();
if (desktopFile.endsWith('.desktop')) {
let fileContents = GLib.file_get_contents(dir + '/' + desktopFile);
let matches = /Icon=(\S+)/.exec(decoder.decode(fileContents[1]));
if (matches && matches[1]) {
if (desktopFile.toLowerCase().includes(formattedClass)) {
icon = matches[1];
break;
}
}
}
}
enumerator.close(null);
if (icon) break;
}
return Utils.lookUpIcon(icon) ? icon : "image-missing";
}
const dispatch = (ws: string) => hyprland.messageAsync(`dispatch workspace ${ws}`).catch(print);
function Workspaces() {
const activeId = hyprland.active.workspace.bind("id");
let workspaceButtons = new Map();
function createWorkspaceButton(id: Number) {
const button = Widget.Button({
on_clicked: () => dispatch(`${id}`),
child: Widget.Label(`${id}`),
class_name: activeId.as(i => `${i === id ? "active" : ""}`),
});
return button;
}
function updateWorkspaceButtons(workspaces: Workspace[]): Array<Button<any, any>> {
workspaces.sort((a, b) => a.id - b.id);
const updatedButtons = new Map();
workspaces.forEach(({ id }) => {
if (workspaceButtons.has(id)) {
updatedButtons.set(id, workspaceButtons.get(id));
} else {
updatedButtons.set(id, createWorkspaceButton(id));
}
});
if (workspaceButtons != updatedButtons)
workspaceButtons = updatedButtons;
return Array.from(workspaceButtons.values());
}
const workspaceButtonsArray = hyprland.bind("workspaces").as(updateWorkspaceButtons);
return Widget.EventBox({
onScrollUp: () => dispatch('+1'),
onScrollDown: () => dispatch('-1'),
child: Widget.Box({
children: workspaceButtonsArray,
class_name: "workspaces",
}),
});
}
function Clock() {
return Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "clock",
children: [
Widget.Label({
class_name: "time",
label: time.bind(),
}),
Widget.Label({
class_name: "date",
label: date.bind(),
})
],
})
}
function BatteryLabel() {
const icon = battery.bind("icon_name");
const isVisible = battery.bind("percent").as(p => p < 100);
return Widget.Box({
class_name: "battery",
visible: isVisible,
children: [
Widget.Icon({ icon }),
Widget.Label({
label: battery.bind("percent").as(p => `${p > 0 ? p : 0}%`),
}),
],
});
}
function SysTray() {
const items = systemtray.bind("items")
.as(items => items.map(item => {
if (item.id.trim() != "nm-applet") {
return Widget.Button({
child: Widget.Icon({ icon: item.bind("icon") }),
on_primary_click: (_, event) => item.activate(event),
on_secondary_click: (_, event) => item.openMenu(event),
tooltip_markup: item.bind("tooltip_markup"),
})
} else {
return undefined
}
}))
// @ts-ignore
return Widget.Box({
class_name: "tray",
spacing: 5,
children: items,
})
}
function AppLauncher() {
const button = Widget.Button({
class_name: "filled_tonal_button awesome_icon",
on_clicked: () => {
App.toggleWindow("applauncher")
},
child: Widget.Label(
"\uf002"
)
})
return button
}
function Wifi() {
const button = Widget.Button({
class_name: "bar_wifi awesome_icon",
on_primary_click: () => {
OpenSettings("network")
},
on_secondary_click: (_, event) => {
const nm_applet = systemtray.items.find(item => item.id == "nm-applet")
if (nm_applet) {
nm_applet.openMenu(event)
} else {
Utils.execAsync("nm-connection-editor").catch(print)
}
},
child: Widget.Icon({
icon: "network-wireless-offline-symbolic"
}),
tooltip_text: 'Disabled'
}).hook(network, self => {
if (network.wifi.enabled) {
self.tooltip_text = network.wifi.ssid || 'Unknown';
self.child.icon = network.wifi.icon_name;
} else {
self.tooltip_text = 'Disabled'
self.child.icon = "network-wireless-offline-symbolic";
}
})
return button
}
function MediaPlayer() {
let metadata = mpris.players[0]?.metadata;
const button = Widget.Button({
class_name: "filled_tonal_button awesome_icon",
on_primary_click: () => {
App.toggleWindow("media")
// Utils.execAsync(["ags", "-t", "media"])
},
on_secondary_click: () => {
Utils.execAsync(["playerctl", "play-pause"]).catch(print)
},
child: Widget.Label(
""
),
visible: false,
tooltip_text: "Unknown"
}).hook(mpris, self => {
if (mpris.players.length > 0) {
self.visible = true;
metadata = mpris.players[0]?.metadata;
if (metadata)
self.tooltip_text = `${metadata["xesam:artist"]} - ${metadata["xesam:title"]}`;
} else {
self.visible = false;
self.tooltip_text = "Unknown"
App.closeWindow("media")
}
})
return button
}
function KeyboardLayout() {
const widget = Widget.Label({
class_name: "keyboard",
label: keyboard_layout.bind()
})
return widget
}
function OpenSideBar() {
const button = Widget.Button({
class_name: "filled_tonal_button awesome_icon",
on_clicked: () => {
App.toggleWindow("sidebar")
// Utils.execAsync(["ags", "-t", "sidebar"])
},
label: ""
})
return button
}
function TaskBar() {
let globalWidgets: Button<Icon<any>, any>[] = [];
function Clients(clients: Client[]) {
const currentClientIds = clients.map(client => client.pid);
globalWidgets = globalWidgets.filter(widget => currentClientIds.includes(widget.attribute.pid));
clients.forEach(item => {
let widget = globalWidgets.find(w => w.attribute.pid === item.pid);
//if (item.class == "kitty") {
// return;
//}
if (widget) {
widget.tooltip_markup = item.title;
} else {
let icon: string | undefined;
if (item.class == "com.github.Aylur.ags") {
if (item.initialTitle == "Settings") {
icon = "emblem-system-symbolic"
}
}
else
icon = getIconNameFromClass(item.class)
widget = Widget.Button({
attribute: { pid: item.pid },
child: Widget.Icon({ icon: icon }),
tooltip_markup: item.title,
});
globalWidgets.push(widget);
}
});
return globalWidgets;
}
return Widget.Box({
class_name: "tray",
spacing: 5,
children: hyprland.bind("clients").as(Clients),
})
}
function volumeIndicator() {
return Widget.EventBox({
onScrollUp: () => audio.speaker.volume += 0.01,
onScrollDown: () => audio.speaker.volume -= 0.01,
class_name: "filled_tonal_button volume_box",
child: Widget.Button({
on_primary_click: () => Utils.execAsync("pavucontrol").catch(print),
on_secondary_click: () => audio.speaker.is_muted = !audio.speaker.is_muted,
child: Widget.Box({
children: [
Widget.Icon().hook(audio.speaker, self => {
const vol = audio.speaker.volume * 100;
const icon = [
[101, 'overamplified'],
[67, 'high'],
[34, 'medium'],
[1, 'low'],
[0, 'muted'],
].find(([threshold]) => Number(threshold) <= vol)?.[1];
if (audio.speaker.is_muted)
self.icon = `audio-volume-muted-symbolic`;
else
self.icon = `audio-volume-${icon}-symbolic`;
self.tooltip_text = `Volume ${Math.floor(vol)}%`;
}),
Widget.Label({
label: audio.speaker.bind("volume").as(volume => `${Math.floor(volume * 100)}%`),
})
]
})
})
})
}
function Left() {
return Widget.Box({
// margin_left: 15,
class_name: "modules-left",
hpack: "start",
spacing: 8,
children: [
AppLauncher(),
MediaPlayer(),
TaskBar()
],
})
}
function Center() {
return Widget.Box({
class_name: "modules-center",
hpack: "center",
spacing: 8,
children: [
Workspaces(),
],
})
}
function Right() {
return Widget.Box({
// margin_right: 15,
class_name: "modules-right",
hpack: "end",
spacing: 8,
children: [
volumeIndicator(),
KeyboardLayout(),
BatteryLabel(),
SysTray(),
Wifi(),
Clock(),
OpenSideBar()
],
})
}
export const Bar = async (monitor = 0) => {
return Widget.Window({
name: `bar-${monitor}`,
class_name: "bar",
monitor,
//anchor: ["top", "left", "right"],
anchor: ["bottom", "left", "right"],
exclusivity: "exclusive",
child: Widget.CenterBox({
start_widget: Left(),
center_widget: Center(),
end_widget: Right(),
}),
})
}
export const BarCornerTopLeft = (monitor = 0) => Widget.Window({
monitor,
name: `bar_corner_tl${monitor}`,
class_name: "transparent",
layer: 'top',
anchor: ['top', 'left'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('top_left', { className: 'corner', }),
setup: enableClickThrough,
});
export const BarCornerTopRight = (monitor = 0) => Widget.Window({
monitor,
name: `bar_corner_tr${monitor}`,
class_name: "transparent",
layer: 'top',
anchor: ['top', 'right'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('top_right', { className: 'corner', }),
setup: enableClickThrough,
});