hyprpanel

This commit is contained in:
wt committed 2024-10-23 21:12:15 +07:00
1 parent 10080a83ee
commit 5151ac24da
322 files changed
+38276

No files matched your search

@@ -0,0 +1,113 @@
import { BoxWidget } from 'lib/types/widget';
const network = await Service.import('network');
const bluetooth = await Service.import('bluetooth');
const notifications = await Service.import('notifications');
const audio = await Service.import('audio');
const Controls = (): BoxWidget => {
return Widget.Box({
class_name: 'dashboard-card controls-container',
hpack: 'fill',
vpack: 'fill',
expand: true,
children: [
Widget.Button({
tooltip_text: 'Toggle Wifi',
expand: true,
setup: (self) => {
self.hook(network, () => {
return (self.class_name = `dashboard-button wifi ${!network.wifi.enabled ? 'disabled' : ''}`);
});
},
on_primary_click: () => network.toggleWifi(),
child: Widget.Label({
class_name: 'txt-icon',
setup: (self) => {
self.hook(network, () => {
return (self.label = network.wifi.enabled ? '󰤨' : '󰤭');
});
},
}),
}),
Widget.Button({
tooltip_text: 'Toggle Bluetooth',
expand: true,
class_name: bluetooth
.bind('enabled')
.as((btOn) => `dashboard-button bluetooth ${!btOn ? 'disabled' : ''}`),
on_primary_click: () => bluetooth.toggle(),
child: Widget.Label({
class_name: 'txt-icon',
label: bluetooth.bind('enabled').as((btOn) => (btOn ? '󰂯' : '󰂲')),
}),
}),
Widget.Button({
tooltip_text: 'Toggle Notifications',
expand: true,
class_name: notifications
.bind('dnd')
.as((dnd) => `dashboard-button notifications ${dnd ? 'disabled' : ''}`),
on_primary_click: () => (notifications.dnd = !notifications.dnd),
child: Widget.Label({
class_name: 'txt-icon',
label: notifications.bind('dnd').as((dnd) => (dnd ? '󰂛' : '󰂚')),
}),
}),
Widget.Button({
tooltip_text: 'Toggle Mute (Playback)',
expand: true,
on_primary_click: () => (audio.speaker.is_muted = !audio.speaker.is_muted),
setup: (self) => {
self.hook(
audio.speaker,
() => {
return (self.class_name = `dashboard-button playback ${audio.speaker.is_muted ? 'disabled' : ''}`);
},
'notify::is-muted',
);
},
child: Widget.Label({
class_name: 'txt-icon',
setup: (self) => {
self.hook(
audio.speaker,
() => {
return (self.label = audio.speaker.is_muted ? '󰖁' : '󰕾');
},
'notify::is-muted',
);
},
}),
}),
Widget.Button({
tooltip_text: 'Toggle Mute (Microphone)',
expand: true,
on_primary_click: () => (audio.microphone.is_muted = !audio.microphone.is_muted),
setup: (self) => {
self.hook(
audio.microphone,
() => {
return (self.class_name = `dashboard-button input ${audio.microphone.is_muted ? 'disabled' : ''}`);
},
'notify::is-muted',
);
},
child: Widget.Label({
class_name: 'txt-icon',
setup: (self) => {
self.hook(
audio.microphone,
() => {
return (self.label = audio.microphone.is_muted ? '󰍭' : '󰍬');
},
'notify::is-muted',
);
},
}),
}),
],
});
};
export { Controls };
@@ -0,0 +1,121 @@
import { BoxWidget } from 'lib/types/widget';
import options from 'options';
const { left, right } = options.menus.dashboard.directories;
const Directories = (): BoxWidget => {
return Widget.Box({
class_name: 'dashboard-card directories-container',
vpack: 'fill',
hpack: 'fill',
expand: true,
children: [
Widget.Box({
vertical: true,
expand: true,
class_name: 'section right',
children: [
Widget.Button({
hpack: 'start',
expand: true,
class_name: 'directory-link left top',
on_primary_click: left.directory1.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: 'start',
label: left.directory1.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: 'start',
class_name: 'directory-link left middle',
on_primary_click: left.directory2.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: 'start',
label: left.directory2.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: 'start',
class_name: 'directory-link left bottom',
on_primary_click: left.directory3.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: 'start',
label: left.directory3.label.bind('value'),
}),
}),
],
}),
Widget.Box({
vertical: true,
expand: true,
class_name: 'section left',
children: [
Widget.Button({
hpack: 'start',
expand: true,
class_name: 'directory-link right top',
on_primary_click: right.directory1.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: 'start',
label: right.directory1.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: 'start',
class_name: 'directory-link right middle',
on_primary_click: right.directory2.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: 'start',
label: right.directory2.label.bind('value'),
}),
}),
Widget.Button({
expand: true,
hpack: 'start',
class_name: 'directory-link right bottom',
on_primary_click: right.directory3.command.bind('value').as((cmd) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(cmd);
};
}),
child: Widget.Label({
hpack: 'start',
label: right.directory3.label.bind('value'),
}),
}),
],
}),
],
});
};
export { Directories };
@@ -0,0 +1,52 @@
import DropdownMenu from '../shared/dropdown/index.js';
import { Profile } from './profile/index.js';
import { Shortcuts } from './shortcuts/index.js';
import { Controls } from './controls/index.js';
import { Stats } from './stats/index.js';
import { Directories } from './directories/index.js';
import Window from 'types/widgets/window.js';
import { Attribute, Child } from 'lib/types/widget.js';
import options from 'options.js';
const { controls, shortcuts, stats, directories } = options.menus.dashboard;
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'dashboardmenu',
transition: options.menus.transition.bind('value'),
child: Widget.Box({
class_name: 'dashboard-menu-content',
css: 'padding: 1px; margin: -1px;',
vexpand: false,
children: [
Widget.Box({
class_name: 'dashboard-content-container',
vertical: true,
children: Utils.merge(
[
controls.enabled.bind('value'),
shortcuts.enabled.bind('value'),
stats.enabled.bind('value'),
directories.enabled.bind('value'),
],
(isControlsEnabled, isShortcutsEnabled, isStatsEnabled, isDirectoriesEnabled) => {
return [
Widget.Box({
class_name: 'dashboard-content-items',
vertical: true,
children: [
Profile(),
...(isShortcutsEnabled ? [Shortcuts()] : []),
...(isControlsEnabled ? [Controls()] : []),
...(isDirectoriesEnabled ? [Directories()] : []),
...(isStatsEnabled ? [Stats()] : []),
],
}),
];
},
),
}),
],
}),
});
};
@@ -0,0 +1,110 @@
import powermenu from '../../power/helpers/actions.js';
import { PowerOptions } from 'lib/types/options.js';
import GdkPixbuf from 'gi://GdkPixbuf';
import options from 'options';
import { BoxWidget, Child } from 'lib/types/widget.js';
import Label from 'types/widgets/label.js';
const { image, name } = options.menus.dashboard.powermenu.avatar;
const { confirmation, shutdown, logout, sleep, reboot } = options.menus.dashboard.powermenu;
const Profile = (): BoxWidget => {
const handleClick = (action: PowerOptions): void => {
const actions = {
shutdown: shutdown.value,
reboot: reboot.value,
logout: logout.value,
sleep: sleep.value,
};
App.closeWindow('dashboardmenu');
if (!confirmation.value) {
Utils.execAsync(actions[action]).catch((err) =>
console.error(`Failed to execute ${action} command. Error: ${err}`),
);
} else {
powermenu.action(action);
}
};
const getIconForButton = (txtIcon: string): Label<Child> => {
return Widget.Label({
className: 'txt-icon',
label: txtIcon,
});
};
return Widget.Box({
class_name: 'profiles-container',
hpack: 'fill',
hexpand: true,
children: [
Widget.Box({
class_name: 'profile-picture-container dashboard-card',
hexpand: true,
vertical: true,
children: [
Widget.Box({
hpack: 'center',
class_name: 'profile-picture',
css: image.bind('value').as((i) => {
try {
GdkPixbuf.Pixbuf.new_from_file(i);
return `background-image: url("${i}")`;
} catch {
return `background-image: url("${App.configDir}/assets/hyprpanel.png")`;
}
}),
}),
Widget.Label({
hpack: 'center',
class_name: 'profile-name',
label: name.bind('value').as((v) => {
if (v === 'system') {
return Utils.exec('bash -c whoami');
}
return v;
}),
}),
],
}),
Widget.Box({
class_name: 'power-menu-container dashboard-card',
vertical: true,
vexpand: true,
children: [
Widget.Button({
class_name: 'dashboard-button shutdown',
on_clicked: () => handleClick('shutdown'),
tooltip_text: 'Shut Down',
vexpand: true,
child: getIconForButton('󰐥'),
}),
Widget.Button({
class_name: 'dashboard-button restart',
on_clicked: () => handleClick('reboot'),
tooltip_text: 'Restart',
vexpand: true,
child: getIconForButton('󰜉'),
}),
Widget.Button({
class_name: 'dashboard-button lock',
on_clicked: () => handleClick('logout'),
tooltip_text: 'Log Out',
vexpand: true,
child: getIconForButton('󰿅'),
}),
Widget.Button({
class_name: 'dashboard-button sleep',
on_clicked: () => handleClick('sleep'),
tooltip_text: 'Sleep',
vexpand: true,
child: getIconForButton('󰤄'),
}),
],
}),
],
});
};
export { Profile };
@@ -0,0 +1,317 @@
const hyprland = await Service.import('hyprland');
import { Attribute, BoxWidget, Child } from 'lib/types/widget';
import options from 'options';
import { Variable as VarType } from 'types/variable';
import Box from 'types/widgets/box';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
const { left, right } = options.menus.dashboard.shortcuts;
const Shortcuts = (): BoxWidget => {
const isRecording = Variable(false, {
poll: [
1000,
`${App.configDir}/services/screen_record.sh status`,
(out): boolean => {
if (out === 'recording') {
return true;
}
return false;
},
],
});
const handleClick = (action: string, tOut: number = 250): void => {
App.closeWindow('dashboardmenu');
setTimeout(() => {
Utils.execAsync(action)
.then((res) => {
return res;
})
.catch((err) => err);
}, tOut);
};
const recordingDropdown = Widget.Menu({
class_name: 'dropdown recording',
hpack: 'fill',
hexpand: true,
setup: (self) => {
const renderMonitorList = (): void => {
const displays = hyprland.monitors.map((mon) => {
return Widget.MenuItem({
label: `Display ${mon.name}`,
on_activate: () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`${App.configDir}/services/screen_record.sh start ${mon.name}`).catch(
(err) => console.error(err),
);
},
});
});
// NOTE: This is disabled since window recording isn't available on wayland
// const apps = hyprland.clients.map((clt) => {
// return Widget.MenuItem({
// label: `${clt.class.charAt(0).toUpperCase() + clt.class.slice(1)} (Workspace ${clt.workspace.name})`,
// on_activate: () => {
// App.closeWindow('dashboardmenu');
// Utils.execAsync(
// `${App.configDir}/services/screen_record.sh start ${clt.focusHistoryID}`,
// ).catch((err) => console.error(err));
// },
// });
// });
self.children = [
...displays,
// Disabled since window recording isn't available on wayland
// ...apps
];
};
self.hook(hyprland, renderMonitorList, 'monitor-added');
self.hook(hyprland, renderMonitorList, 'monitor-removed');
},
});
type ShortcutFixed = {
tooltip: string;
command: string;
icon: string;
configurable: false;
};
type ShortcutVariable = {
tooltip: VarType<string>;
command: VarType<string>;
icon: VarType<string>;
configurable?: true;
};
type Shortcut = ShortcutFixed | ShortcutVariable;
const cmdLn = (sCut: ShortcutVariable): boolean => {
return sCut.command.value.length > 0;
};
const leftCardHidden = Variable(
!(cmdLn(left.shortcut1) || cmdLn(left.shortcut2) || cmdLn(left.shortcut3) || cmdLn(left.shortcut4)),
);
const createButton = (shortcut: Shortcut, className: string): Button<Label<Attribute>, Attribute> => {
if (shortcut.configurable !== false) {
return Widget.Button({
vexpand: true,
tooltip_text: shortcut.tooltip.value,
class_name: className,
on_primary_click: () => handleClick(shortcut.command.value),
child: Widget.Label({
class_name: 'button-label txt-icon',
label: shortcut.icon.value,
}),
});
} else {
// handle non-configurable shortcut
return Widget.Button({
vexpand: true,
tooltip_text: shortcut.tooltip,
class_name: className,
on_primary_click: (_, event) => {
if (shortcut.command === 'settings-dialog') {
App.closeWindow('dashboardmenu');
App.toggleWindow('settings-dialog');
} else if (shortcut.command === 'record') {
if (isRecording.value === true) {
App.closeWindow('dashboardmenu');
return Utils.execAsync(`${App.configDir}/services/screen_record.sh stop`).catch((err) =>
console.error(err),
);
} else {
recordingDropdown.popup_at_pointer(event);
}
}
},
child: Widget.Label({
class_name: 'button-label txt-icon',
label: shortcut.icon,
}),
});
}
};
const createButtonIfCommandExists = (
shortcut: Shortcut,
className: string,
command: string,
): Button<Label<Attribute>, Attribute> | Box<Child, Attribute> => {
if (command.length > 0) {
return createButton(shortcut, className);
}
return Widget.Box();
};
return Widget.Box({
class_name: 'shortcuts-container',
hpack: 'fill',
hexpand: true,
children: [
Widget.Box({
child: Utils.merge(
[
left.shortcut1.command.bind('value'),
left.shortcut2.command.bind('value'),
left.shortcut1.tooltip.bind('value'),
left.shortcut2.tooltip.bind('value'),
left.shortcut1.icon.bind('value'),
left.shortcut2.icon.bind('value'),
left.shortcut3.command.bind('value'),
left.shortcut4.command.bind('value'),
left.shortcut3.tooltip.bind('value'),
left.shortcut4.tooltip.bind('value'),
left.shortcut3.icon.bind('value'),
left.shortcut4.icon.bind('value'),
],
() => {
const isVisibleLeft = cmdLn(left.shortcut1) || cmdLn(left.shortcut2);
const isVisibleRight = cmdLn(left.shortcut3) || cmdLn(left.shortcut4);
if (!isVisibleLeft && !isVisibleRight) {
leftCardHidden.value = true;
return Widget.Box();
}
leftCardHidden.value = false;
return Widget.Box({
class_name: 'container most-used dashboard-card',
children: [
Widget.Box({
className: `card-button-section-container ${isVisibleRight && isVisibleLeft ? 'visible' : ''}`,
child: isVisibleLeft
? Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
left.shortcut1,
`dashboard-button top-button ${cmdLn(left.shortcut2) ? 'paired' : ''}`,
left.shortcut1.command.value,
),
createButtonIfCommandExists(
left.shortcut2,
'dashboard-button',
left.shortcut2.command.value,
),
],
})
: Widget.Box({
children: [],
}),
}),
Widget.Box({
className: 'card-button-section-container',
child: isVisibleRight
? Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
left.shortcut3,
`dashboard-button top-button ${cmdLn(left.shortcut4) ? 'paired' : ''}`,
left.shortcut3.command.value,
),
createButtonIfCommandExists(
left.shortcut4,
'dashboard-button',
left.shortcut4.command.value,
),
],
})
: Widget.Box({
children: [],
}),
}),
],
});
},
),
}),
Widget.Box({
child: Utils.merge(
[
right.shortcut1.command.bind('value'),
right.shortcut1.tooltip.bind('value'),
right.shortcut1.icon.bind('value'),
right.shortcut3.command.bind('value'),
right.shortcut3.tooltip.bind('value'),
right.shortcut3.icon.bind('value'),
leftCardHidden.bind('value'),
isRecording.bind('value'),
],
() => {
return Widget.Box({
class_name: `container utilities dashboard-card ${!leftCardHidden.value ? 'paired' : ''}`,
children: [
Widget.Box({
className: `card-button-section-container visible`,
child: Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
right.shortcut1,
'dashboard-button top-button paired',
right.shortcut1.command.value,
),
createButtonIfCommandExists(
{
tooltip: 'HyprPanel Configuration',
command: 'settings-dialog',
icon: '󰒓',
configurable: false,
},
'dashboard-button',
'settings-dialog',
),
],
}),
}),
Widget.Box({
className: 'card-button-section-container',
child: Widget.Box({
vertical: true,
hexpand: true,
vexpand: true,
children: [
createButtonIfCommandExists(
right.shortcut3,
'dashboard-button top-button paired',
right.shortcut3.command.value,
),
createButtonIfCommandExists(
{
tooltip: 'Record Screen',
command: 'record',
icon: '󰑊',
configurable: false,
},
`dashboard-button record ${isRecording.value ? 'active' : ''}`,
'record',
),
],
}),
}),
],
});
},
),
}),
],
});
};
export { Shortcuts };
@@ -0,0 +1,362 @@
import options from 'options';
import { GPU_Stat } from 'lib/types/gpustat';
import { dependencies } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
import { GenericResourceMetrics } from 'lib/types/customModules/generic';
const { terminal } = options;
const { enable_gpu } = options.menus.dashboard.stats;
const Stats = (): BoxWidget => {
const divide = ([total, free]: number[]): number => free / total;
const formatSizeInGB = (sizeInKB: number): number => Number((sizeInKB / 1024 ** 2).toFixed(2));
const cpu = Variable(0, {
poll: [
2000,
'top -b -n 1',
(out): number => {
if (typeof out !== 'string') {
return 0;
}
const cpuOut = out.split('\n').find((line) => line.includes('Cpu(s)'));
if (cpuOut === undefined) {
return 0;
}
const freeCpu = parseFloat(cpuOut.split(/\s+/)[1].replace(',', '.'));
return divide([100, freeCpu]);
},
],
});
const ram = Variable(
{ total: 0, used: 0, percentage: 0 },
{
poll: [
2000,
'free',
(out): GenericResourceMetrics => {
if (typeof out !== 'string') {
return { total: 0, used: 0, percentage: 0 };
}
const ramOut = out.split('\n').find((line) => line.includes('Mem:'));
if (ramOut === undefined) {
return { total: 0, used: 0, percentage: 0 };
}
const [totalRam, usedRam] = ramOut.split(/\s+/).splice(1, 2).map(Number);
return {
percentage: divide([totalRam, usedRam]),
total: formatSizeInGB(totalRam),
used: formatSizeInGB(usedRam),
};
},
],
},
);
const gpu = Variable(0);
const GPUStat = Widget.Box({
child: enable_gpu.bind('value').as((gpStat) => {
if (!gpStat || !dependencies('gpustat')) {
return Widget.Box();
}
return Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: 'stat gpu',
hexpand: true,
vpack: 'center',
setup: (self) => {
const getGpuUsage = (): void => {
if (!enable_gpu.value) {
gpu.value = 0;
return;
}
Utils.execAsync('gpustat --json')
.then((out) => {
if (typeof out !== 'string') {
return 0;
}
try {
const data = JSON.parse(out);
const totalGpu = 100;
const usedGpu =
data.gpus.reduce((acc: number, gpu: GPU_Stat) => {
return acc + gpu['utilization.gpu'];
}, 0) / data.gpus.length;
gpu.value = divide([totalGpu, usedGpu]);
} catch (e) {
console.error('Error getting GPU stats:', e);
gpu.value = 0;
}
})
.catch((err) => {
console.error(`An error occurred while fetching GPU stats: ${err}`);
});
};
self.poll(2000, getGpuUsage);
Utils.merge([gpu.bind('value'), enable_gpu.bind('value')], (gpu, enableGpu) => {
if (!enableGpu) {
return (self.children = []);
}
return (self.children = [
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return (): void => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.Label({
class_name: 'txt-icon',
label: '󰢮',
}),
}),
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return (): void => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.LevelBar({
class_name: 'stats-bar',
hexpand: true,
vpack: 'center',
value: gpu,
}),
}),
]);
});
},
}),
Widget.Box({
hpack: 'end',
children: Utils.merge([gpu.bind('value'), enable_gpu.bind('value')], (gpuUsed, enableGpu) => {
if (!enableGpu) {
return [];
}
return [
Widget.Label({
class_name: 'stat-value gpu',
label: `${Math.floor(gpuUsed * 100)}%`,
}),
];
}),
}),
],
});
}),
});
const storage = Variable(
{ total: 0, used: 0, percentage: 0 },
{
poll: [
2000,
'df -B1 /',
(out): GenericResourceMetrics => {
if (typeof out !== 'string') {
return { total: 0, used: 0, percentage: 0 };
}
const dfOut = out.split('\n').find((line) => line.startsWith('/'));
if (dfOut === undefined) {
return { total: 0, used: 0, percentage: 0 };
}
const parts = dfOut.split(/\s+/);
const size = parseInt(parts[1], 10);
const used = parseInt(parts[2], 10);
const sizeInGB = formatSizeInGB(size);
const usedInGB = formatSizeInGB(used);
return {
total: Math.floor(sizeInGB / 1000),
used: Math.floor(usedInGB / 1000),
percentage: divide([size, used]),
};
},
],
},
);
return Widget.Box({
class_name: 'dashboard-card stats-container',
vertical: true,
vpack: 'fill',
hpack: 'fill',
expand: true,
children: [
Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: 'stat cpu',
hexpand: true,
vpack: 'center',
children: [
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.Label({
class_name: 'txt-icon',
label: '',
}),
}),
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.LevelBar({
class_name: 'stats-bar',
hexpand: true,
vpack: 'center',
bar_mode: 'continuous',
max_value: 1,
value: cpu.bind('value'),
}),
}),
],
}),
Widget.Label({
hpack: 'end',
class_name: 'stat-value cpu',
label: cpu.bind('value').as((v) => `${Math.floor(v * 100)}%`),
}),
],
}),
Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: 'stat ram',
vpack: 'center',
hexpand: true,
children: [
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.Label({
class_name: 'txt-icon',
label: '',
}),
}),
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.LevelBar({
class_name: 'stats-bar',
hexpand: true,
vpack: 'center',
value: ram.bind('value').as((v) => v.percentage),
}),
}),
],
}),
Widget.Label({
hpack: 'end',
class_name: 'stat-value ram',
label: ram.bind('value').as((v) => `${v.used}/${v.total} GB`),
}),
],
}),
GPUStat,
Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: 'stat storage',
hexpand: true,
vpack: 'center',
children: [
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.Label({
class_name: 'txt-icon',
label: '󰋊',
}),
}),
Widget.Button({
on_primary_click: terminal.bind('value').as((term) => {
return () => {
App.closeWindow('dashboardmenu');
Utils.execAsync(`bash -c "${term} -e btop"`).catch(
(err) => `Failed to open btop: ${err}`,
);
};
}),
child: Widget.LevelBar({
class_name: 'stats-bar',
hexpand: true,
vpack: 'center',
value: storage.bind('value').as((v) => v.percentage),
}),
}),
],
}),
Widget.Label({
hpack: 'end',
class_name: 'stat-value storage',
label: storage.bind('value').as((v) => `${v.used}/${v.total} GB`),
}),
],
}),
],
});
};
export { Stats };