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,69 @@
import { BoxWidget } from 'lib/types/widget';
const network = await Service.import('network');
const Ethernet = (): BoxWidget => {
return Widget.Box({
class_name: 'menu-section-container ethernet',
vertical: true,
children: [
Widget.Box({
class_name: 'menu-label-container',
hpack: 'fill',
child: Widget.Label({
class_name: 'menu-label',
hexpand: true,
hpack: 'start',
label: 'Ethernet',
}),
}),
Widget.Box({
class_name: 'menu-items-section',
vertical: true,
child: Widget.Box({
class_name: 'menu-content',
vertical: true,
setup: (self) => {
self.hook(network, () => {
return (self.child = Widget.Box({
class_name: 'network-element-item',
child: Widget.Box({
hpack: 'start',
children: [
Widget.Icon({
class_name: `network-icon ethernet ${network.wired.state === 'activated' ? 'active' : ''}`,
tooltip_text: network.wired.internet,
icon: `${network.wired['icon_name']}`,
}),
Widget.Box({
class_name: 'connection-container',
vertical: true,
children: [
Widget.Label({
class_name: 'active-connection',
hpack: 'start',
truncate: 'end',
wrap: true,
label: `Ethernet Connection ${network.wired.state !== 'unknown' && typeof network.wired?.speed === 'number' ? `(${network.wired?.speed / 1000} Gbps)` : ''}`,
}),
Widget.Label({
hpack: 'start',
class_name: 'connection-status dim',
label:
network.wired.internet.charAt(0).toUpperCase() +
network.wired.internet.slice(1),
}),
],
}),
],
}),
}));
});
},
}),
}),
],
});
};
export { Ethernet };
@@ -0,0 +1,22 @@
import Window from 'types/widgets/window.js';
import DropdownMenu from '../shared/dropdown/index.js';
import { Ethernet } from './ethernet/index.js';
import { Wifi } from './wifi/index.js';
import { Attribute, Child } from 'lib/types/widget.js';
import options from 'options.js';
export default (): Window<Child, Attribute> => {
return DropdownMenu({
name: 'networkmenu',
transition: options.menus.transition.bind('value'),
child: Widget.Box({
class_name: 'menu-items network',
child: Widget.Box({
vertical: true,
hexpand: true,
class_name: 'menu-items-container network',
children: [Ethernet(), Wifi()],
}),
}),
});
};
@@ -0,0 +1,23 @@
import { WifiIcon } from 'lib/types/network';
const getWifiIcon = (iconName: string): WifiIcon => {
const deviceIconMap: [string, WifiIcon][] = [
['network-wireless-acquiring', '󰤩'],
['network-wireless-connected', '󰤨'],
['network-wireless-encrypted', '󰤪'],
['network-wireless-hotspot', '󰤨'],
['network-wireless-no-route', '󰤩'],
['network-wireless-offline', '󰤮'],
['network-wireless-signal-excellent', '󰤨'],
['network-wireless-signal-good', '󰤥'],
['network-wireless-signal-ok', '󰤢'],
['network-wireless-signal-weak', '󰤟'],
['network-wireless-signal-none', '󰤯'],
];
const foundMatch = deviceIconMap.find((icon) => RegExp(icon[0]).test(iconName.toLowerCase()));
return foundMatch ? foundMatch[1] : '󰤨';
};
export { getWifiIcon };
@@ -0,0 +1,104 @@
import { Network } from 'types/service/network';
import { Variable } from 'types/variable';
import { AccessPoint } from 'lib/types/network';
import Box from 'types/widgets/box';
import { Attribute, Child } from 'lib/types/widget';
const renderWapStaging = (
self: Box<Child, Attribute>,
network: Network,
staging: Variable<AccessPoint>,
connecting: Variable<string>,
): void => {
Utils.merge([network.bind('wifi'), staging.bind('value')], () => {
if (!Object.keys(staging.value).length) {
return (self.child = Widget.Box());
}
return (self.child = Widget.Box({
class_name: 'network-element-item staging',
vertical: true,
children: [
Widget.Box({
hpack: 'fill',
hexpand: true,
children: [
Widget.Icon({
class_name: `network-icon wifi`,
icon: `${staging.value.iconName}`,
}),
Widget.Box({
class_name: 'connection-container',
hexpand: true,
vertical: true,
children: [
Widget.Label({
class_name: 'active-connection',
hpack: 'start',
truncate: 'end',
wrap: true,
label: staging.value.ssid,
}),
],
}),
Widget.Revealer({
hpack: 'end',
reveal_child: connecting.bind('value').as((c) => staging.value.bssid === c),
child: Widget.Spinner({
class_name: 'spinner wap',
}),
}),
],
}),
Widget.Box({
class_name: 'network-password-input-container',
hpack: 'fill',
hexpand: true,
children: [
Widget.Entry({
hpack: 'start',
hexpand: true,
visibility: false,
class_name: 'network-password-input',
placeholder_text: 'enter password',
onAccept: (selfInp) => {
connecting.value = staging.value.bssid || '';
Utils.execAsync(
`nmcli dev wifi connect ${staging.value.bssid} password ${selfInp.text}`,
)
.catch((err) => {
connecting.value = '';
console.error(`Failed to connect to wifi: ${staging.value.ssid}... ${err}`);
Utils.notify({
summary: 'Network',
body: err,
timeout: 5000,
});
})
.then(() => {
connecting.value = '';
staging.value = {} as AccessPoint;
});
selfInp.text = '';
},
}),
Widget.Button({
hpack: 'end',
class_name: 'close-network-password-input-button',
on_primary_click: () => {
connecting.value = '';
staging.value = {} as AccessPoint;
},
child: Widget.Icon({
class_name: 'close-network-password-input-icon',
icon: 'window-close-symbolic',
}),
}),
],
}),
],
}));
});
};
export { renderWapStaging };
@@ -0,0 +1,219 @@
import { Network } from 'types/service/network.js';
import { AccessPoint, WifiStatus } from 'lib/types/network.js';
import { Variable } from 'types/variable.js';
import { getWifiIcon } from '../utils.js';
import { WIFI_STATUS_MAP } from 'globals/network.js';
import { Attribute, Child } from 'lib/types/widget.js';
import Box from 'types/widgets/box.js';
const renderWAPs = (
self: Box<Child, Attribute>,
network: Network,
staging: Variable<AccessPoint>,
connecting: Variable<string>,
): void => {
const getIdBySsid = (ssid: string, nmcliOutput: string): string | undefined => {
const lines = nmcliOutput.trim().split('\n');
for (const line of lines) {
const columns = line.trim().split(/\s{2,}/);
if (columns[0].includes(ssid)) {
return columns[1];
}
}
};
const isValidWifiStatus = (status: string): status is WifiStatus => {
return status in WIFI_STATUS_MAP;
};
const getWifiStatus = (): string => {
const wifiState = network.wifi.state?.toLowerCase();
if (wifiState && isValidWifiStatus(wifiState)) {
return WIFI_STATUS_MAP[wifiState];
}
return WIFI_STATUS_MAP['unknown'];
};
self.hook(network, () => {
Utils.merge([staging.bind('value'), connecting.bind('value')], () => {
// NOTE: Sometimes the network service will yield a "this._device is undefined" when
// trying to access the "access_points" property. So we must validate that
// it's not 'undefined'
// --
// Also this is an AGS bug that needs to be fixed
// TODO: Remove @ts-ignore once AGS bug is fixed
// @ts-expect-error to fix AGS bug
let WAPs = network.wifi._device !== undefined ? network.wifi['access_points'] : [];
const dedupeWAPs = (): AccessPoint[] => {
const dedupMap: Record<string, AccessPoint> = {};
WAPs.forEach((item: AccessPoint) => {
if (item.ssid !== null && !Object.hasOwnProperty.call(dedupMap, item.ssid)) {
dedupMap[item.ssid] = item;
}
});
return Object.keys(dedupMap).map((itm) => dedupMap[itm]);
};
WAPs = dedupeWAPs();
const isInStaging = (wap: AccessPoint): boolean => {
if (Object.keys(staging.value).length === 0) {
return false;
}
return wap.bssid === staging.value.bssid;
};
const isDisconnecting = (wap: AccessPoint): boolean => {
if (wap.ssid === network.wifi.ssid) {
return network.wifi.state.toLowerCase() === 'deactivating';
}
return false;
};
const filteredWAPs = WAPs.filter((ap: AccessPoint) => {
return ap.ssid !== 'Unknown' && !isInStaging(ap);
}).sort((a: AccessPoint, b: AccessPoint) => {
if (network.wifi.ssid === a.ssid) {
return -1;
}
if (network.wifi.ssid === b.ssid) {
return 1;
}
return b.strength - a.strength;
});
if (filteredWAPs.length <= 0 && Object.keys(staging.value).length === 0) {
return (self.child = Widget.Label({
class_name: 'waps-not-found dim',
expand: true,
hpack: 'center',
vpack: 'center',
label: 'No Wi-Fi Networks Found',
}));
}
return (self.children = filteredWAPs.map((ap: AccessPoint) => {
return Widget.Box({
children: [
Widget.Button({
on_primary_click: () => {
if (ap.bssid === connecting.value || ap.active) {
return;
}
connecting.value = ap.bssid || '';
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`)
.then(() => {
connecting.value = '';
staging.value = {} as AccessPoint;
})
.catch((err) => {
if (err.toLowerCase().includes('secrets were required, but not provided')) {
staging.value = ap;
} else {
Utils.notify({
summary: 'Network',
body: err,
timeout: 5000,
});
}
connecting.value = '';
});
},
class_name: 'network-element-item',
child: Widget.Box({
hexpand: true,
children: [
Widget.Box({
hpack: 'start',
hexpand: true,
children: [
Widget.Label({
vpack: 'start',
class_name: `network-icon wifi ${ap.ssid === network.wifi.ssid ? 'active' : ''} txt-icon`,
label: getWifiIcon(`${ap['iconName']}`),
}),
Widget.Box({
class_name: 'connection-container',
vpack: 'center',
vertical: true,
children: [
Widget.Label({
vpack: 'center',
class_name: 'active-connection',
hpack: 'start',
truncate: 'end',
wrap: true,
label: ap.ssid,
}),
Widget.Revealer({
revealChild: ap.ssid === network.wifi.ssid,
child: Widget.Label({
hpack: 'start',
class_name: 'connection-status dim',
label: getWifiStatus(),
}),
}),
],
}),
],
}),
Widget.Revealer({
hpack: 'end',
vpack: 'start',
reveal_child: ap.bssid === connecting.value || isDisconnecting(ap),
child: Widget.Spinner({
vpack: 'start',
class_name: 'spinner wap',
}),
}),
],
}),
}),
Widget.Revealer({
vpack: 'start',
reveal_child: ap.bssid !== connecting.value && ap.active,
child: Widget.Button({
tooltip_text: 'Delete/Forget Network',
class_name: 'menu-icon-button network disconnect',
on_primary_click: () => {
connecting.value = ap.bssid || '';
Utils.execAsync('nmcli connection show --active').then(() => {
Utils.execAsync('nmcli connection show --active').then((res) => {
const connectionId = getIdBySsid(ap.ssid || '', res);
if (connectionId === undefined) {
console.error(
`Error while forgetting "${ap.ssid}": Connection ID not found`,
);
return;
}
Utils.execAsync(`nmcli connection delete ${connectionId} "${ap.ssid}"`)
.then(() => (connecting.value = ''))
.catch((err) => {
connecting.value = '';
console.error(`Error while forgetting "${ap.ssid}": ${err}`);
});
});
});
},
child: Widget.Label({
class_name: 'txt-icon delete-network',
label: '󰚃',
}),
}),
}),
],
});
}));
});
});
};
export { renderWAPs };
@@ -0,0 +1,81 @@
const network = await Service.import('network');
import { renderWAPs } from './WirelessAPs.js';
import { renderWapStaging } from './APStaging.js';
import { AccessPoint } from 'lib/types/network.js';
import { BoxWidget } from 'lib/types/widget.js';
const Staging = Variable({} as AccessPoint);
const Connecting = Variable('');
const searchInProgress = Variable(false);
const startRotation = (): void => {
searchInProgress.value = true;
setTimeout(() => {
searchInProgress.value = false;
}, 5 * 1000);
};
const Wifi = (): BoxWidget => {
return Widget.Box({
class_name: 'menu-section-container wifi',
vertical: true,
children: [
Widget.Box({
class_name: 'menu-label-container',
hpack: 'fill',
children: [
Widget.Label({
class_name: 'menu-label',
hexpand: true,
hpack: 'start',
label: 'Wi-Fi',
}),
Widget.Switch({
class_name: 'menu-switch network',
vpack: 'center',
tooltip_text: 'Toggle Wifi',
active: network.wifi.enabled,
on_activate: () => {
network.toggleWifi();
},
}),
Widget.Button({
vpack: 'center',
hpack: 'end',
class_name: 'menu-icon-button search network',
on_primary_click: () => {
startRotation();
network.wifi.scan();
},
child: Widget.Icon({
class_name: searchInProgress.bind('value').as((v) => (v ? 'spinning' : '')),
icon: 'view-refresh-symbolic',
}),
}),
],
}),
Widget.Box({
class_name: 'menu-items-section',
vertical: true,
children: [
Widget.Box({
class_name: 'wap-staging',
setup: (self) => {
renderWapStaging(self, network, Staging, Connecting);
},
}),
Widget.Box({
class_name: 'available-waps',
vertical: true,
setup: (self) => {
renderWAPs(self, network, Staging, Connecting);
},
}),
],
}),
],
});
};
export { Wifi };