hyprpanel
This commit is contained in:
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 };
|
||||
Reference in new issue
Block a user