307 lines
12 KiB
JavaScript

import App from 'resource:///com/github/Aylur/ags/app.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import { MaterialIcon } from './materialicon.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { languages } from './statusicons_languages.js';
// A guessing func to try to support langs not listed in data/languages.js
function isLanguageMatch(abbreviation, word) {
const lowerAbbreviation = abbreviation.toLowerCase();
const lowerWord = word.toLowerCase();
let j = 0;
for (let i = 0; i < lowerWord.length; i++) {
if (lowerWord[i] === lowerAbbreviation[j]) {
j++;
}
if (j === lowerAbbreviation.length) {
return true;
}
}
return false;
}
export const MicMuteIndicator = () => Widget.Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationSmall,
revealChild: false,
setup: (self) => self.hook(Audio, (self) => {
self.revealChild = Audio.microphone?.stream?.isMuted;
}),
child: MaterialIcon('mic_off', 'norm'),
});
export const NotificationIndicator = (notifCenterName = 'sideright') => {
const widget = Widget.Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationSmall,
revealChild: false,
setup: (self) => self
.hook(Notifications, (self, id) => {
if (!id || Notifications.dnd) return;
if (!Notifications.getNotification(id)) return;
self.revealChild = true;
}, 'notified')
.hook(App, (self, currentName, visible) => {
if (visible && currentName === notifCenterName) {
self.revealChild = false;
}
})
,
child: Widget.Box({
children: [
MaterialIcon('notifications', 'norm'),
Widget.Label({
className: 'txt-small titlefont',
attribute: {
unreadCount: 0,
update: (self) => self.label = `${self.attribute.unreadCount}`,
},
setup: (self) => self
.hook(Notifications, (self, id) => {
if (!id || Notifications.dnd) return;
if (!Notifications.getNotification(id)) return;
self.attribute.unreadCount++;
self.attribute.update(self);
}, 'notified')
.hook(App, (self, currentName, visible) => {
if (visible && currentName === notifCenterName) {
self.attribute.unreadCount = 0;
self.attribute.update(self);
}
})
,
})
]
})
});
return widget;
}
export const BluetoothIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'false': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_disabled' }),
'true': Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth' }),
},
setup: (self) => self
.hook(Bluetooth, stack => {
stack.shown = String(Bluetooth.enabled);
})
,
});
const BluetoothDevices = () => Widget.Box({
className: 'spacing-h-5',
setup: self => self.hook(Bluetooth, self => {
self.children = Bluetooth.connected_devices.map((device) => {
return Widget.Box({
className: 'bar-bluetooth-device spacing-h-5',
vpack: 'center',
tooltipText: device.name,
children: [
Widget.Icon(`${device.iconName}-symbolic`),
...(device.batteryPercentage ? [Widget.Label({
className: 'txt-smallie',
label: `${device.batteryPercentage}`,
setup: (self) => {
self.hook(device, (self) => {
self.label = `${device.batteryPercentage}`;
}, 'notify::batteryPercentage')
}
})] : []),
]
});
});
self.visible = Bluetooth.connected_devices.length > 0;
}, 'notify::connected-devices'),
})
const NetworkWiredIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'fallback': SimpleNetworkIndicator(),
'unknown': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
'connected': Widget.Label({ className: 'txt-norm icon-material', label: 'lan' }),
'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
},
setup: (self) => self.hook(Network, stack => {
if (!Network.wired)
return;
const { internet } = Network.wired;
if (['connecting', 'connected'].includes(internet))
stack.shown = internet;
else if (Network.connectivity !== 'full')
stack.shown = 'disconnected';
else
stack.shown = 'fallback';
}),
});
const SimpleNetworkIndicator = () => Widget.Icon({
setup: (self) => self.hook(Network, self => {
const icon = Network[Network.primary || 'wifi']?.iconName;
self.icon = icon || '';
self.visible = icon;
}),
});
const NetworkWifiIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'disabled': Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' }),
'disconnected': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' }),
'connecting': Widget.Label({ className: 'txt-norm icon-material', label: 'settings_ethernet' }),
'0': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' }),
'1': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_1_bar' }),
'2': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_2_bar' }),
'3': Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_3_bar' }),
'4': Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_4_bar' }),
},
setup: (self) => self.hook(Network, (stack) => {
if (!Network.wifi) {
return;
}
if (Network.wifi.internet == 'connected') {
stack.shown = String(Math.ceil(Network.wifi.strength / 25));
}
else if (["disconnected", "connecting"].includes(Network.wifi.internet)) {
stack.shown = Network.wifi.internet;
}
}),
});
export const NetworkIndicator = () => Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: {
'fallback': SimpleNetworkIndicator(),
'wifi': NetworkWifiIndicator(),
'wired': NetworkWiredIndicator(),
},
setup: (self) => self.hook(Network, stack => {
if (!Network.primary) {
stack.shown = 'wifi';
return;
}
const primary = Network.primary || 'fallback';
if (['wifi', 'wired'].includes(primary))
stack.shown = primary;
else
stack.shown = 'fallback';
}),
});
const HyprlandXkbKeyboardLayout = async ({ useFlag } = {}) => {
try {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
var languageStackArray = [];
const updateCurrentKeyboards = () => {
var initLangs = [];
JSON.parse(Utils.exec('hyprctl -j devices')).keyboards
.forEach(keyboard => {
initLangs.push(...keyboard.layout.split(',').map(lang => lang.trim()));
});
initLangs = [...new Set(initLangs)];
languageStackArray = Array.from({ length: initLangs.length }, (_, i) => {
const lang = languages.find(lang => lang.layout == initLangs[i]);
// if (!lang) return [
// initLangs[i],
// Widget.Label({ label: initLangs[i] })
// ];
// return [
// lang.layout,
// Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
// ];
// Object
if (!lang) return {
[initLangs[i]]: Widget.Label({ label: initLangs[i] })
};
return {
[lang.layout]: Widget.Label({ label: (useFlag ? lang.flag : lang.layout) })
};
});
};
updateCurrentKeyboards();
const widgetRevealer = Widget.Revealer({
transition: 'slide_left',
transitionDuration: userOptions.animations.durationSmall,
revealChild: languageStackArray.length > 1,
});
const widgetKids = {
...languageStackArray.reduce((obj, lang) => {
return { ...obj, ...lang };
}, {}),
'undef': Widget.Label({ label: '?' }),
}
const widgetContent = Widget.Stack({
transition: 'slide_up_down',
transitionDuration: userOptions.animations.durationSmall,
children: widgetKids,
setup: (self) => self.hook(Hyprland, (stack, kbName, layoutName) => {
if (!kbName) {
return;
}
var lang = languages.find(lang => layoutName.includes(lang.name));
if (lang) {
widgetContent.shown = lang.layout;
}
else { // Attempt to support langs not listed
lang = languageStackArray.find(lang => isLanguageMatch(lang[0], layoutName));
if (!lang) stack.shown = 'undef';
else stack.shown = lang[0];
}
}, 'keyboard-layout'),
});
widgetRevealer.child = widgetContent;
return widgetRevealer;
} catch {
return null;
}
}
const OptionalKeyboardLayout = async () => {
try {
return await HyprlandXkbKeyboardLayout({ useFlag: userOptions.appearance.keyboardUseFlag });
} catch {
return null;
}
};
const createKeyboardLayoutInstances = async () => {
const Hyprland = (await import('resource:///com/github/Aylur/ags/service/hyprland.js')).default;
const monitorsCount = Hyprland.monitors.length
const instances = await Promise.all(
Array.from({ length: monitorsCount }, () => OptionalKeyboardLayout())
);
return instances;
};
const optionalKeyboardLayoutInstances = await createKeyboardLayoutInstances()
export const StatusIcons = (props = {}, monitor = 0) => Widget.Box({
...props,
child: Widget.Box({
className: 'spacing-h-15',
children: [
MicMuteIndicator(),
optionalKeyboardLayoutInstances[monitor],
NotificationIndicator(),
NetworkIndicator(),
Widget.Box({
className: 'spacing-h-5',
children: [BluetoothIndicator(), BluetoothDevices()]
})
]
})
});