Upgrade to Agsv2 + Astal (#533)
* migrate to astal * Reorganize project structure. * progress * Migrate Dashboard and Window Title modules. * Migrate clock and notification bar modules. * Remove unused code * Media menu * Rework network and volume modules * Finish custom modules. * Migrate battery bar module. * Update battery module and organize helpers. * Migrate workspace module. * Wrap up bar modules. * Checkpoint before I inevitbly blow something up. * Updates * Fix event propagation logic. * Type fixes * More type fixes * Fix padding for event boxes. * Migrate volume menu and refactor scroll event handlers. * network module WIP * Migrate network service. * Migrate bluetooth menu * Updates * Migrate notifications * Update scrolling behavior for custom modules. * Improve popup notifications and add timer functionality. * Migration notifications menu header/controls. * Migrate notifications menu and consolidate notifications menu code. * Migrate power menu. * Dashboard progress * Migrate dashboard * Migrate media menu. * Reduce media menu nesting. * Finish updating media menu bindings to navigate active player. * Migrate battery menu * Consolidate code * Migrate calendar menu * Fix workspace logic to update on client add/change/remove and consolidate code. * Migrate osd * Consolidate hyprland service connections. * Implement startup dropdown menu position allocation. * Migrate settings menu (WIP) * Settings dialo menu fixes * Finish Dashboard menu * Type updates * update submoldule for types * update github ci * ci * Submodule update * Ci updates * Remove type checking for now. * ci fix * Fix a bunch of stuff, losing track... need rest. Brb coffee * Validate dropdown menu before render. * Consolidate code and add auto-hide functionality. * Improve auto-hide behavior. * Consolidate audio menu code * Organize bluetooth code * Improve active player logic * Properly dismiss a notification on action button resolution. * Implement CLI command engine and migrate CLI commands. * Handle variable disposal * Bar component fixes and add hyprland startup rules. * Handle potentially null bindings network and bluetooth bindings. * Handle potentially null wired adapter. * Fix GPU stats * Handle poller for GPU * Fix gpu bar logic. * Clean up logic for stat bars. * Handle wifi and wired bar icon bindings. * Fix battery percentages * Fix switch behavior * Wifi staging fixes * Reduce redundant hyprland service calls. * Code cleanup * Document the option code and reduce redundant calls to optimize performance. * Remove outdated comment. * Add JSDocs * Add meson to build hyprpanel * Consistency updates * Organize commands * Fix images not showing up on notifications. * Remove todo * Move hyprpanel configuration to the ~/.config/hyprpanel directory and add utility commands. * Handle SRC directory for the bundled/built hyprpanel. * Add namespaces to all windows * Migrate systray * systray updates * Update meson to include ts, tsx and scss files. * Remove log from meson * Fix file choose path and make it float. * Added a command to check the dependency status * Update dep names. * Get scale directly from env * Add todo
This commit is contained in:
50
src/lib/behaviors/autoHide.ts
Normal file
50
src/lib/behaviors/autoHide.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { bind, Variable } from 'astal';
|
||||
import { hyprlandService } from '../constants/services';
|
||||
import { App } from 'astal/gtk3';
|
||||
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
|
||||
import { forceUpdater } from 'src/components/bar/modules/workspaces/helpers';
|
||||
import options from 'src/options';
|
||||
|
||||
const { autoHide } = options.bar;
|
||||
|
||||
const focusedClient = (focusedClient: AstalHyprland.Client): void => {
|
||||
const fullscreenBinding = bind(focusedClient, 'fullscreen');
|
||||
|
||||
if (!focusedClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
Variable.derive([bind(fullscreenBinding)], (isFullScreen) => {
|
||||
if (autoHide.get() === 'fullscreen') {
|
||||
App.get_window(`bar-${focusedClient.monitor.id}`)?.set_visible(!isFullScreen);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const initializeAutoHide = (): void => {
|
||||
Variable.derive([bind(autoHide), bind(forceUpdater), bind(hyprlandService, 'workspaces')], (shouldAutohide) => {
|
||||
if (shouldAutohide === 'never') {
|
||||
hyprlandService.monitors.forEach((monitor) => {
|
||||
App.get_window(`bar-${monitor.id}`)?.set_visible(true);
|
||||
});
|
||||
}
|
||||
|
||||
hyprlandService.workspaces.map((workspace) => {
|
||||
if (autoHide.get() === 'single-window') {
|
||||
App.get_window(`bar-${workspace.monitor.id}`)?.set_visible(workspace.clients.length !== 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Variable.derive([bind(hyprlandService, 'focusedClient')], (currentClient) => {
|
||||
focusedClient(currentClient);
|
||||
});
|
||||
|
||||
Variable.derive([bind(autoHide)], (shouldAutohide) => {
|
||||
if (shouldAutohide === 'fullscreen') {
|
||||
hyprlandService.workspaces.forEach((workspace) => {
|
||||
App.get_window(`bar-${workspace.monitor.id}`)?.set_visible(!workspace.hasFullscreen);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
28
src/lib/behaviors/batteryWarning.ts
Normal file
28
src/lib/behaviors/batteryWarning.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { batteryService } from '../constants/services';
|
||||
import icons from '../icons/icons';
|
||||
import { Notify } from '../utils';
|
||||
|
||||
export function warnOnLowBattery(): void {
|
||||
batteryService.connect('notify::percent', () => {
|
||||
const { lowBatteryThreshold, lowBatteryNotification, lowBatteryNotificationText, lowBatteryNotificationTitle } =
|
||||
options.menus.power;
|
||||
|
||||
if (!lowBatteryNotification.get() || batteryService.charging) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lowThreshold = lowBatteryThreshold.get();
|
||||
|
||||
if (batteryService.percentage === lowThreshold || batteryService.percentage === lowThreshold / 2) {
|
||||
Notify({
|
||||
summary: lowBatteryNotificationTitle
|
||||
.get()
|
||||
.replace('/$POWER_LEVEL/g', batteryService.percentage.toString()),
|
||||
body: lowBatteryNotificationText.get().replace('/$POWER_LEVEL/g', batteryService.percentage.toString()),
|
||||
iconName: icons.ui.warning,
|
||||
urgency: 'critical',
|
||||
timeout: 7000,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
23
src/lib/behaviors/hyprlandRules.ts
Normal file
23
src/lib/behaviors/hyprlandRules.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { execAsync } from 'astal';
|
||||
import { hyprlandService } from '../constants/services';
|
||||
|
||||
const floatSettingsDialog = (): void => {
|
||||
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^(hyprpanel-settings)$"']);
|
||||
|
||||
hyprlandService.connect('config-reloaded', () => {
|
||||
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^(hyprpanel-settings)$"']);
|
||||
});
|
||||
};
|
||||
|
||||
const floatFilePicker = (): void => {
|
||||
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^((Save|Import) Hyprpanel.*)$"']);
|
||||
|
||||
hyprlandService.connect('config-reloaded', () => {
|
||||
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^((Save|Import) Hyprpanel.*)$"']);
|
||||
});
|
||||
};
|
||||
|
||||
export const hyprlandSettings = (): void => {
|
||||
floatSettingsDialog();
|
||||
floatFilePicker();
|
||||
};
|
||||
10
src/lib/behaviors/index.ts
Normal file
10
src/lib/behaviors/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import './autoHide';
|
||||
import { initializeAutoHide } from './autoHide';
|
||||
import { warnOnLowBattery } from './batteryWarning';
|
||||
import { hyprlandSettings } from './hyprlandRules';
|
||||
|
||||
export const initializeSystemBehaviors = (): void => {
|
||||
warnOnLowBattery();
|
||||
initializeAutoHide();
|
||||
hyprlandSettings();
|
||||
};
|
||||
142
src/lib/constants/colors.ts
Normal file
142
src/lib/constants/colors.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
export const namedColors = new Set([
|
||||
'alice blue',
|
||||
'antique white',
|
||||
'aqua',
|
||||
'aquamarine',
|
||||
'azure',
|
||||
'beige',
|
||||
'bisque',
|
||||
'black',
|
||||
'blanched almond',
|
||||
'blue',
|
||||
'blue violet',
|
||||
'brown',
|
||||
'burlywood',
|
||||
'cadet blue',
|
||||
'chartreuse',
|
||||
'chocolate',
|
||||
'coral',
|
||||
'cornflower blue',
|
||||
'cornsilk',
|
||||
'crimson',
|
||||
'cyan',
|
||||
'dark blue',
|
||||
'dark cyan',
|
||||
'dark goldenrod',
|
||||
'dark gray',
|
||||
'dark green',
|
||||
'dark khaki',
|
||||
'dark magenta',
|
||||
'dark olive green',
|
||||
'dark orange',
|
||||
'dark orchid',
|
||||
'dark red',
|
||||
'dark salmon',
|
||||
'dark sea green',
|
||||
'dark slate blue',
|
||||
'dark slate gray',
|
||||
'dark turquoise',
|
||||
'dark violet',
|
||||
'deep pink',
|
||||
'deep sky blue',
|
||||
'dim gray',
|
||||
'dodger blue',
|
||||
'firebrick',
|
||||
'floral white',
|
||||
'forest green',
|
||||
'fuchsia',
|
||||
'gainsboro',
|
||||
'ghost white',
|
||||
'gold',
|
||||
'goldenrod',
|
||||
'gray',
|
||||
'green',
|
||||
'green yellow',
|
||||
'honeydew',
|
||||
'hot pink',
|
||||
'indian red',
|
||||
'indigo',
|
||||
'ivory',
|
||||
'khaki',
|
||||
'lavender',
|
||||
'lavender blush',
|
||||
'lawn green',
|
||||
'lemon chiffon',
|
||||
'light blue',
|
||||
'light coral',
|
||||
'light cyan',
|
||||
'light goldenrod yellow',
|
||||
'light green',
|
||||
'light grey',
|
||||
'light pink',
|
||||
'light salmon',
|
||||
'light sea green',
|
||||
'light sky blue',
|
||||
'light slate gray',
|
||||
'light steel blue',
|
||||
'light yellow',
|
||||
'lime',
|
||||
'lime green',
|
||||
'linen',
|
||||
'magenta',
|
||||
'maroon',
|
||||
'medium aquamarine',
|
||||
'medium blue',
|
||||
'medium orchid',
|
||||
'medium purple',
|
||||
'medium sea green',
|
||||
'medium slate blue',
|
||||
'medium spring green',
|
||||
'medium turquoise',
|
||||
'medium violet red',
|
||||
'midnight blue',
|
||||
'mint cream',
|
||||
'misty rose',
|
||||
'moccasin',
|
||||
'navajo white',
|
||||
'navy',
|
||||
'old lace',
|
||||
'olive',
|
||||
'olive drab',
|
||||
'orange',
|
||||
'orange red',
|
||||
'orchid',
|
||||
'pale goldenrod',
|
||||
'pale green',
|
||||
'pale turquoise',
|
||||
'pale violet red',
|
||||
'papaya whip',
|
||||
'peach puff',
|
||||
'peru',
|
||||
'pink',
|
||||
'plum',
|
||||
'powder blue',
|
||||
'purple',
|
||||
'red',
|
||||
'rosy brown',
|
||||
'royal blue',
|
||||
'saddle brown',
|
||||
'salmon',
|
||||
'sandy brown',
|
||||
'sea green',
|
||||
'seashell',
|
||||
'sienna',
|
||||
'silver',
|
||||
'sky blue',
|
||||
'slate blue',
|
||||
'slate gray',
|
||||
'snow',
|
||||
'spring green',
|
||||
'steel blue',
|
||||
'tan',
|
||||
'teal',
|
||||
'thistle',
|
||||
'tomato',
|
||||
'turquoise',
|
||||
'violet',
|
||||
'wheat',
|
||||
'white',
|
||||
'white smoke',
|
||||
'yellow',
|
||||
'yellow green',
|
||||
]);
|
||||
31
src/lib/constants/distro.ts
Normal file
31
src/lib/constants/distro.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const distroIcons = [
|
||||
['deepin', ''],
|
||||
['fedora', ''],
|
||||
['arch', ''],
|
||||
['nixos', ''],
|
||||
['debian', ''],
|
||||
['opensuse-tumbleweed', ''],
|
||||
['ubuntu', ''],
|
||||
['endeavouros', ''],
|
||||
['manjaro', ''],
|
||||
['popos', ''],
|
||||
['garuda', ''],
|
||||
['zorin', ''],
|
||||
['mxlinux', ''],
|
||||
['arcolinux', ''],
|
||||
['gentoo', ''],
|
||||
['artix', ''],
|
||||
['centos', ''],
|
||||
['hyperbola', ''],
|
||||
['kubuntu', ''],
|
||||
['mandriva', ''],
|
||||
['xerolinux', ''],
|
||||
['parabola', ''],
|
||||
['void', ''],
|
||||
['linuxmint', ''],
|
||||
['archlabs', ''],
|
||||
['devuan', ''],
|
||||
['freebsd', ''],
|
||||
['openbsd', ''],
|
||||
['slackware', ''],
|
||||
];
|
||||
22
src/lib/constants/network.ts
Normal file
22
src/lib/constants/network.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
|
||||
type DeviceSate = AstalNetwork.DeviceState;
|
||||
type DevceStates = {
|
||||
[key in DeviceSate]: string;
|
||||
};
|
||||
|
||||
export const DEVICE_STATES: DevceStates = {
|
||||
[AstalNetwork.DeviceState.UNKNOWN]: 'Unknown',
|
||||
[AstalNetwork.DeviceState.UNMANAGED]: 'Unmanaged',
|
||||
[AstalNetwork.DeviceState.UNAVAILABLE]: 'Unavailable',
|
||||
[AstalNetwork.DeviceState.DISCONNECTED]: 'Disconnected',
|
||||
[AstalNetwork.DeviceState.PREPARE]: 'Prepare',
|
||||
[AstalNetwork.DeviceState.CONFIG]: 'Config',
|
||||
[AstalNetwork.DeviceState.NEED_AUTH]: 'Need Authentication',
|
||||
[AstalNetwork.DeviceState.IP_CONFIG]: 'IP Configuration',
|
||||
[AstalNetwork.DeviceState.IP_CHECK]: 'IP Check',
|
||||
[AstalNetwork.DeviceState.SECONDARIES]: 'Secondaries',
|
||||
[AstalNetwork.DeviceState.ACTIVATED]: 'Activated',
|
||||
[AstalNetwork.DeviceState.DEACTIVATING]: 'Deactivating',
|
||||
[AstalNetwork.DeviceState.FAILED]: 'Failed',
|
||||
};
|
||||
37
src/lib/constants/options.ts
Normal file
37
src/lib/constants/options.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import { DropdownMenuList } from '../types/options';
|
||||
|
||||
export const StackTransitionMap = {
|
||||
none: Gtk.StackTransitionType.NONE,
|
||||
crossfade: Gtk.StackTransitionType.CROSSFADE,
|
||||
slide_right: Gtk.StackTransitionType.SLIDE_RIGHT,
|
||||
slide_left: Gtk.StackTransitionType.SLIDE_LEFT,
|
||||
slide_up: Gtk.StackTransitionType.SLIDE_UP,
|
||||
slide_down: Gtk.StackTransitionType.SLIDE_DOWN,
|
||||
};
|
||||
|
||||
export const RevealerTransitionMap = {
|
||||
none: Gtk.RevealerTransitionType.NONE,
|
||||
crossfade: Gtk.RevealerTransitionType.CROSSFADE,
|
||||
slide_right: Gtk.RevealerTransitionType.SLIDE_RIGHT,
|
||||
slide_left: Gtk.RevealerTransitionType.SLIDE_LEFT,
|
||||
slide_up: Gtk.RevealerTransitionType.SLIDE_UP,
|
||||
slide_down: Gtk.RevealerTransitionType.SLIDE_DOWN,
|
||||
};
|
||||
|
||||
export const dropdownMenuList = [
|
||||
'dashboardmenu',
|
||||
'audiomenu',
|
||||
'mediamenu',
|
||||
'networkmenu',
|
||||
'bluetoothmenu',
|
||||
'notificationsmenu',
|
||||
'calendarmenu',
|
||||
'energymenu',
|
||||
'powerdropdownmenu',
|
||||
'settings-dialog',
|
||||
] as const;
|
||||
|
||||
export const isDropdownMenu = (name: string): name is DropdownMenuList => {
|
||||
return dropdownMenuList.includes(name as DropdownMenuList);
|
||||
};
|
||||
34
src/lib/constants/services.ts
Normal file
34
src/lib/constants/services.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* NOTE: This approach is not recommended if the program is going to be
|
||||
* running as a client.
|
||||
* ---------------------------------------
|
||||
* Hyprpanel will not be, so this is fine.
|
||||
* ---------------------------------------
|
||||
*/
|
||||
import Hyprland from 'gi://AstalHyprland';
|
||||
export const hyprlandService = Hyprland.get_default();
|
||||
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
export const mprisService = AstalMpris.get_default();
|
||||
|
||||
import AstalWp from 'gi://AstalWp?version=0.1';
|
||||
const wireplumber = AstalWp.get_default() as AstalWp.Wp;
|
||||
export const audioService = wireplumber.audio;
|
||||
|
||||
import AstalNetwork from 'gi://AstalNetwork?version=0.1';
|
||||
export const networkService = AstalNetwork.get_default();
|
||||
|
||||
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
|
||||
export const bluetoothService = AstalBluetooth.get_default();
|
||||
|
||||
import AstalBattery from 'gi://AstalBattery?version=0.1';
|
||||
export const batteryService = AstalBattery.get_default();
|
||||
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
export const notifdService = AstalNotifd.get_default();
|
||||
|
||||
import Brightness from 'src/services/Brightness';
|
||||
export const brightnessService = Brightness.get_default();
|
||||
|
||||
import AstalPowerProfiles from 'gi://AstalPowerProfiles?version=0.1';
|
||||
export const powerProfilesService = AstalPowerProfiles.get_default();
|
||||
29
src/lib/constants/workspaces.ts
Normal file
29
src/lib/constants/workspaces.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const defaultApplicationIcons = {
|
||||
'[dD]iscord': '',
|
||||
'^thunderbird': '',
|
||||
'class:wezterm$': '',
|
||||
'draw.io': '',
|
||||
'firefox-developer-edition': '',
|
||||
'google-chrome': '',
|
||||
'title:YouTube ': '',
|
||||
Spotify: '',
|
||||
chromium: '',
|
||||
code: '',
|
||||
dbeaver: '',
|
||||
edge: '',
|
||||
evince: '',
|
||||
firefox: '',
|
||||
foot: '',
|
||||
keepassxc: '',
|
||||
keymapp: '',
|
||||
kitty: '',
|
||||
obsidian: '',
|
||||
password$: '',
|
||||
qBittorrent$: '',
|
||||
rofi: '',
|
||||
slack: '',
|
||||
spotube: '',
|
||||
steam: '',
|
||||
telegram: '',
|
||||
vlc: '',
|
||||
};
|
||||
144
src/lib/icons/icons.ts
Normal file
144
src/lib/icons/icons.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
export const substitutes = {
|
||||
'transmission-gtk': 'transmission',
|
||||
'blueberry.py': 'blueberry',
|
||||
Caprine: 'facebook-messenger',
|
||||
'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
|
||||
'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
|
||||
'audio-headset-bluetooth': 'audio-headphones-symbolic',
|
||||
'audio-card-analog-usb': 'audio-speakers-symbolic',
|
||||
'audio-card-analog-pci': 'audio-card-symbolic',
|
||||
'preferences-system': 'emblem-system-symbolic',
|
||||
'com.github.Aylur.ags-symbolic': 'controls-symbolic',
|
||||
'com.github.Aylur.ags': 'controls-symbolic',
|
||||
} as const;
|
||||
|
||||
export default {
|
||||
missing: 'image-missing-symbolic',
|
||||
nix: {
|
||||
nix: 'nix-snowflake-symbolic',
|
||||
},
|
||||
app: {
|
||||
terminal: 'terminal-symbolic',
|
||||
},
|
||||
fallback: {
|
||||
executable: 'application-x-executable',
|
||||
notification: 'dialog-information-symbolic',
|
||||
video: 'video-x-generic-symbolic',
|
||||
audio: 'audio-x-generic-symbolic',
|
||||
},
|
||||
ui: {
|
||||
close: 'window-close-symbolic',
|
||||
colorpicker: 'color-select-symbolic',
|
||||
info: 'info-symbolic',
|
||||
link: 'external-link-symbolic',
|
||||
lock: 'system-lock-screen-symbolic',
|
||||
menu: 'open-menu-symbolic',
|
||||
refresh: 'view-refresh-symbolic',
|
||||
search: 'system-search-symbolic',
|
||||
settings: 'emblem-system-symbolic',
|
||||
themes: 'preferences-desktop-theme-symbolic',
|
||||
tick: 'object-select-symbolic',
|
||||
time: 'hourglass-symbolic',
|
||||
toolbars: 'toolbars-symbolic',
|
||||
warning: 'dialog-warning-symbolic',
|
||||
arrow: {
|
||||
right: 'pan-end-symbolic',
|
||||
left: 'pan-start-symbolic',
|
||||
down: 'pan-down-symbolic',
|
||||
up: 'pan-up-symbolic',
|
||||
},
|
||||
},
|
||||
audio: {
|
||||
mic: {
|
||||
muted: 'microphone-disabled-symbolic',
|
||||
low: 'microphone-sensitivity-low-symbolic',
|
||||
medium: 'microphone-sensitivity-medium-symbolic',
|
||||
high: 'microphone-sensitivity-high-symbolic',
|
||||
},
|
||||
volume: {
|
||||
muted: 'audio-volume-muted-symbolic',
|
||||
low: 'audio-volume-low-symbolic',
|
||||
medium: 'audio-volume-medium-symbolic',
|
||||
high: 'audio-volume-high-symbolic',
|
||||
overamplified: 'audio-volume-overamplified-symbolic',
|
||||
},
|
||||
type: {
|
||||
headset: 'audio-headphones-symbolic',
|
||||
speaker: 'audio-speakers-symbolic',
|
||||
card: 'audio-card-symbolic',
|
||||
},
|
||||
mixer: 'mixer-symbolic',
|
||||
},
|
||||
powerprofile: {
|
||||
balanced: 'power-profile-balanced-symbolic',
|
||||
'power-saver': 'power-profile-power-saver-symbolic',
|
||||
performance: 'power-profile-performance-symbolic',
|
||||
},
|
||||
asusctl: {
|
||||
profile: {
|
||||
Balanced: 'power-profile-balanced-symbolic',
|
||||
Quiet: 'power-profile-power-saver-symbolic',
|
||||
Performance: 'power-profile-performance-symbolic',
|
||||
},
|
||||
mode: {
|
||||
Integrated: 'processor-symbolic',
|
||||
Hybrid: 'controller-symbolic',
|
||||
},
|
||||
},
|
||||
battery: {
|
||||
charging: 'battery-flash-symbolic',
|
||||
warning: 'battery-empty-symbolic',
|
||||
},
|
||||
bluetooth: {
|
||||
enabled: 'bluetooth-active-symbolic',
|
||||
disabled: 'bluetooth-disabled-symbolic',
|
||||
},
|
||||
brightness: {
|
||||
indicator: 'display-brightness-symbolic',
|
||||
keyboard: 'keyboard-brightness-symbolic',
|
||||
screen: 'display-brightness-symbolic',
|
||||
},
|
||||
powermenu: {
|
||||
sleep: 'weather-clear-night-symbolic',
|
||||
reboot: 'system-reboot-symbolic',
|
||||
logout: 'system-log-out-symbolic',
|
||||
shutdown: 'system-shutdown-symbolic',
|
||||
},
|
||||
recorder: {
|
||||
recording: 'media-record-symbolic',
|
||||
},
|
||||
notifications: {
|
||||
noisy: 'org.gnome.Settings-notifications-symbolic',
|
||||
silent: 'notifications-disabled-symbolic',
|
||||
message: 'chat-bubbles-symbolic',
|
||||
},
|
||||
trash: {
|
||||
full: 'user-trash-full-symbolic',
|
||||
empty: 'user-trash-symbolic',
|
||||
},
|
||||
mpris: {
|
||||
shuffle: {
|
||||
enabled: 'media-playlist-shuffle-symbolic',
|
||||
disabled: 'media-playlist-consecutive-symbolic',
|
||||
},
|
||||
loop: {
|
||||
none: 'media-playlist-repeat-symbolic',
|
||||
track: 'media-playlist-repeat-song-symbolic',
|
||||
playlist: 'media-playlist-repeat-symbolic',
|
||||
},
|
||||
playing: 'media-playback-pause-symbolic',
|
||||
paused: 'media-playback-start-symbolic',
|
||||
stopped: 'media-playback-start-symbolic',
|
||||
prev: 'media-skip-backward-symbolic',
|
||||
next: 'media-skip-forward-symbolic',
|
||||
},
|
||||
system: {
|
||||
cpu: 'org.gnome.SystemMonitor-symbolic',
|
||||
ram: 'drive-harddisk-solidstate-symbolic',
|
||||
temp: 'temperature-symbolic',
|
||||
},
|
||||
color: {
|
||||
dark: 'dark-mode-symbolic',
|
||||
light: 'light-mode-symbolic',
|
||||
},
|
||||
};
|
||||
198
src/lib/icons/icons2.ts
Normal file
198
src/lib/icons/icons2.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
export const substitutes = {
|
||||
'transmission-gtk': 'transmission',
|
||||
'blueberry.py': 'blueberry',
|
||||
Caprine: 'facebook-messenger',
|
||||
'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
|
||||
'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
|
||||
'audio-headset-bluetooth': 'audio-headphones-symbolic',
|
||||
'audio-card-analog-usb': 'audio-speakers-symbolic',
|
||||
'audio-card-analog-pci': 'audio-card-symbolic',
|
||||
'preferences-system': 'emblem-system-symbolic',
|
||||
'com.github.Aylur.ags-symbolic': 'controls-symbolic',
|
||||
'com.github.Aylur.ags': 'controls-symbolic',
|
||||
};
|
||||
|
||||
export default {
|
||||
missing: 'image-missing-symbolic',
|
||||
nix: {
|
||||
nix: 'nix-snowflake-symbolic',
|
||||
},
|
||||
app: {
|
||||
terminal: 'terminal-symbolic',
|
||||
},
|
||||
fallback: {
|
||||
executable: 'application-x-executable',
|
||||
notification: 'dialog-information-symbolic',
|
||||
video: 'video-x-generic-symbolic',
|
||||
audio: 'audio-x-generic-symbolic',
|
||||
},
|
||||
ui: {
|
||||
close: 'window-close-symbolic',
|
||||
colorpicker: 'color-select-symbolic',
|
||||
info: 'info-symbolic',
|
||||
link: 'external-link-symbolic',
|
||||
lock: 'system-lock-screen-symbolic',
|
||||
menu: 'open-menu-symbolic',
|
||||
refresh: 'view-refresh-symbolic',
|
||||
search: 'system-search-symbolic',
|
||||
settings: 'emblem-system-symbolic',
|
||||
themes: 'preferences-desktop-theme-symbolic',
|
||||
tick: 'object-select-symbolic',
|
||||
time: 'hourglass-symbolic',
|
||||
toolbars: 'toolbars-symbolic',
|
||||
warning: 'dialog-warning-symbolic',
|
||||
arrow: {
|
||||
right: 'pan-end-symbolic',
|
||||
left: 'pan-start-symbolic',
|
||||
down: 'pan-down-symbolic',
|
||||
up: 'pan-up-symbolic',
|
||||
},
|
||||
},
|
||||
audio: {
|
||||
mic: {
|
||||
muted: 'microphone-disabled-symbolic',
|
||||
low: 'microphone-sensitivity-low-symbolic',
|
||||
medium: 'microphone-sensitivity-medium-symbolic',
|
||||
high: 'microphone-sensitivity-high-symbolic',
|
||||
},
|
||||
volume: {
|
||||
muted: 'audio-volume-muted-symbolic',
|
||||
low: 'audio-volume-low-symbolic',
|
||||
medium: 'audio-volume-medium-symbolic',
|
||||
high: 'audio-volume-high-symbolic',
|
||||
overamplified: 'audio-volume-overamplified-symbolic',
|
||||
},
|
||||
type: {
|
||||
headset: 'audio-headphones-symbolic',
|
||||
speaker: 'audio-speakers-symbolic',
|
||||
card: 'audio-card-symbolic',
|
||||
},
|
||||
mixer: 'mixer-symbolic',
|
||||
},
|
||||
powerprofile: {
|
||||
balanced: 'power-profile-balanced-symbolic',
|
||||
'power-saver': 'power-profile-power-saver-symbolic',
|
||||
performance: 'power-profile-performance-symbolic',
|
||||
},
|
||||
asusctl: {
|
||||
profile: {
|
||||
Balanced: 'power-profile-balanced-symbolic',
|
||||
Quiet: 'power-profile-power-saver-symbolic',
|
||||
Performance: 'power-profile-performance-symbolic',
|
||||
},
|
||||
mode: {
|
||||
Integrated: 'processor-symbolic',
|
||||
Hybrid: 'controller-symbolic',
|
||||
},
|
||||
},
|
||||
battery: {
|
||||
charging: 'battery-flash-symbolic',
|
||||
warning: 'battery-empty-symbolic',
|
||||
},
|
||||
bluetooth: {
|
||||
enabled: 'bluetooth-active-symbolic',
|
||||
disabled: 'bluetooth-disabled-symbolic',
|
||||
},
|
||||
brightness: {
|
||||
indicator: 'display-brightness-symbolic',
|
||||
keyboard: 'keyboard-brightness-symbolic',
|
||||
screen: 'display-brightness-symbolic',
|
||||
},
|
||||
powermenu: {
|
||||
sleep: 'weather-clear-night-symbolic',
|
||||
reboot: 'system-reboot-symbolic',
|
||||
logout: 'system-log-out-symbolic',
|
||||
shutdown: 'system-shutdown-symbolic',
|
||||
},
|
||||
recorder: {
|
||||
recording: 'media-record-symbolic',
|
||||
},
|
||||
notifications: {
|
||||
noisy: 'org.gnome.Settings-notifications-symbolic',
|
||||
silent: 'notifications-disabled-symbolic',
|
||||
message: 'chat-bubbles-symbolic',
|
||||
},
|
||||
trash: {
|
||||
full: 'user-trash-full-symbolic',
|
||||
empty: 'user-trash-symbolic',
|
||||
},
|
||||
mpris: {
|
||||
shuffle: {
|
||||
enabled: 'media-playlist-shuffle-symbolic',
|
||||
disabled: 'media-playlist-consecutive-symbolic',
|
||||
},
|
||||
loop: {
|
||||
none: 'media-playlist-repeat-symbolic',
|
||||
track: 'media-playlist-repeat-song-symbolic',
|
||||
playlist: 'media-playlist-repeat-symbolic',
|
||||
},
|
||||
playing: 'media-playback-pause-symbolic',
|
||||
paused: 'media-playback-start-symbolic',
|
||||
stopped: 'media-playback-start-symbolic',
|
||||
prev: 'media-skip-backward-symbolic',
|
||||
next: 'media-skip-forward-symbolic',
|
||||
},
|
||||
system: {
|
||||
cpu: 'org.gnome.SystemMonitor-symbolic',
|
||||
ram: 'drive-harddisk-solidstate-symbolic',
|
||||
temp: 'temperature-symbolic',
|
||||
},
|
||||
color: {
|
||||
dark: 'dark-mode-symbolic',
|
||||
light: 'light-mode-symbolic',
|
||||
},
|
||||
weather: {
|
||||
warning: 'dialog-warning-symbolic',
|
||||
sunny: 'weather-clear-symbolic',
|
||||
clear: 'weather-clear-night-symbolic',
|
||||
partly_cloudy: 'weather-few-clouds-symbolic',
|
||||
partly_cloudy_night: 'weather-few-clouds-night-symbolic',
|
||||
cloudy: 'weather-overcast-symbolic',
|
||||
overcast: 'weather-overcast-symbolic',
|
||||
mist: 'weather-overcast-symbolic',
|
||||
patchy_rain_nearby: 'weather-showers-scattered-symbolic',
|
||||
patchy_rain_possible: 'weather-showers-scattered-symbolic',
|
||||
patchy_snow_possible: 'weather-snow-symbolic',
|
||||
patchy_sleet_possible: 'weather-snow-symbolic',
|
||||
patchy_freezing_drizzle_possible: 'weather-showers-scattered-symbolic',
|
||||
thundery_outbreaks_possible: 'weather-overcast-symbolic',
|
||||
blowing_snow: 'weather-snow-symbolic',
|
||||
blizzard: 'weather-snow-symbolic',
|
||||
fog: 'weather-fog-symbolic',
|
||||
freezing_fog: 'weather-fog-symbolic',
|
||||
patchy_light_drizzle: 'weather-showers-scattered-symbolic',
|
||||
light_drizzle: 'weather-showers-symbolic',
|
||||
freezing_drizzle: 'weather-showers-symbolic',
|
||||
heavy_freezing_drizzle: 'weather-showers-symbolic',
|
||||
patchy_light_rain: 'weather-showers-scattered-symbolic',
|
||||
light_rain: 'weather-showers-symbolic',
|
||||
moderate_rain_at_times: 'weather-showers-symbolic',
|
||||
moderate_rain: 'weather-showers-symbolic',
|
||||
heavy_rain_at_times: 'weather-showers-symbolic',
|
||||
heavy_rain: 'weather-showers-symbolic',
|
||||
light_freezing_rain: 'weather-showers-symbolic',
|
||||
moderate_or_heavy_freezing_rain: 'weather-showers-symbolic',
|
||||
light_sleet: 'weather-snow-symbolic',
|
||||
moderate_or_heavy_sleet: 'weather-snow-symbolic',
|
||||
patchy_light_snow: 'weather-snow-symbolic',
|
||||
light_snow: 'weather-snow-symbolic',
|
||||
patchy_moderate_snow: 'weather-snow-symbolic',
|
||||
moderate_snow: 'weather-snow-symbolic',
|
||||
patchy_heavy_snow: 'weather-snow-symbolic',
|
||||
heavy_snow: 'weather-snow-symbolic',
|
||||
ice_pellets: 'weather-showers-symbolic',
|
||||
light_rain_shower: 'weather-showers-symbolic',
|
||||
moderate_or_heavy_rain_shower: 'weather-showers-symbolic',
|
||||
torrential_rain_shower: 'weather-showers-symbolic',
|
||||
light_sleet_showers: 'weather-showers-symbolic',
|
||||
moderate_or_heavy_sleet_showers: 'weather-showers-symbolic',
|
||||
light_snow_showers: 'weather-snow-symbolic',
|
||||
moderate_or_heavy_snow_showers: 'weather-snow-symbolic',
|
||||
light_showers_of_ice_pellets: 'weather-showers-symbolic',
|
||||
moderate_or_heavy_showers_of_ice_pellets: 'weather-showers-symbolic',
|
||||
patchy_light_rain_with_thunder: 'weather-showers-scattered-symbolic',
|
||||
moderate_or_heavy_rain_with_thunder: 'weather-showers-symbolic',
|
||||
patchy_light_snow_with_thunder: 'weather-snow-symbolic',
|
||||
moderate_or_heavy_snow_with_thunder: 'weather-snow-symbolic',
|
||||
},
|
||||
} as const;
|
||||
54
src/lib/icons/weather.ts
Normal file
54
src/lib/icons/weather.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
export const weatherIcons = {
|
||||
warning: '',
|
||||
sunny: '',
|
||||
clear: '',
|
||||
partly_cloudy: '',
|
||||
partly_cloudy_night: '',
|
||||
cloudy: '',
|
||||
overcast: '',
|
||||
mist: '',
|
||||
patchy_rain_nearby: '',
|
||||
patchy_rain_possible: '',
|
||||
patchy_snow_possible: '',
|
||||
patchy_sleet_possible: '',
|
||||
patchy_freezing_drizzle_possible: '',
|
||||
thundery_outbreaks_possible: '',
|
||||
blowing_snow: '',
|
||||
blizzard: '',
|
||||
fog: '',
|
||||
freezing_fog: '',
|
||||
patchy_light_drizzle: '',
|
||||
light_drizzle: '',
|
||||
freezing_drizzle: '',
|
||||
heavy_freezing_drizzle: '',
|
||||
patchy_light_rain: '',
|
||||
light_rain: '',
|
||||
moderate_rain_at_times: '',
|
||||
moderate_rain: '',
|
||||
heavy_rain_at_times: '',
|
||||
heavy_rain: '',
|
||||
light_freezing_rain: '',
|
||||
moderate_or_heavy_freezing_rain: '',
|
||||
light_sleet: '',
|
||||
moderate_or_heavy_sleet: '',
|
||||
patchy_light_snow: '',
|
||||
light_snow: '',
|
||||
patchy_moderate_snow: '',
|
||||
moderate_snow: '',
|
||||
patchy_heavy_snow: '',
|
||||
heavy_snow: '',
|
||||
ice_pellets: '',
|
||||
light_rain_shower: '',
|
||||
moderate_or_heavy_rain_shower: '',
|
||||
torrential_rain_shower: '',
|
||||
light_sleet_showers: '',
|
||||
moderate_or_heavy_sleet_showers: '',
|
||||
light_snow_showers: '',
|
||||
moderate_or_heavy_snow_showers: '',
|
||||
light_showers_of_ice_pellets: '',
|
||||
moderate_or_heavy_showers_of_ice_pellets: '',
|
||||
patchy_light_rain_with_thunder: '',
|
||||
moderate_or_heavy_rain_with_thunder: '',
|
||||
patchy_light_snow_with_thunder: '',
|
||||
moderate_or_heavy_snow_with_thunder: '',
|
||||
} as const;
|
||||
321
src/lib/option.ts
Normal file
321
src/lib/option.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import { isHexColor } from '../globals/variables';
|
||||
import { MkOptionsResult } from './types/options';
|
||||
import { ensureDirectory } from './session';
|
||||
import Variable from 'astal/variable';
|
||||
import { monitorFile, readFile, writeFile } from 'astal/file';
|
||||
import GLib from 'gi://GLib?version=2.0';
|
||||
|
||||
type OptProps = {
|
||||
persistent?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A file to store default configurations. Placed inside the cache directory.
|
||||
* NOTE: We need to move this out into the .config directory instead.
|
||||
*/
|
||||
export const defaultFile = `${GLib.get_tmp_dir()}/ags/hyprpanel/default.json`;
|
||||
|
||||
export class Opt<T = unknown> extends Variable<T> {
|
||||
/**
|
||||
* The initial value set when the `Opt` is created.
|
||||
*/
|
||||
public readonly initial: T;
|
||||
|
||||
/**
|
||||
* Indicates whether this option should remain unchanged even when reset operations occur.
|
||||
*/
|
||||
public readonly persistent: boolean;
|
||||
|
||||
private _id = '';
|
||||
|
||||
/**
|
||||
* Creates an instance of `Opt`.
|
||||
*
|
||||
* @param {T} initial - The initial value of the option.
|
||||
* @param {OptProps} [props={}] - Additional properties for the option.
|
||||
*/
|
||||
constructor(initial: T, { persistent = false }: OptProps = {}) {
|
||||
super(initial);
|
||||
this.initial = initial;
|
||||
this.persistent = persistent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the current value to a JSON-compatible string.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
toJSON(): string {
|
||||
return `opt:${JSON.stringify(this.get())}`;
|
||||
}
|
||||
|
||||
public get value(): T {
|
||||
return this.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the current value of the option.
|
||||
*/
|
||||
public set value(val: T) {
|
||||
this.set(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the unique ID of the option.
|
||||
*/
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the unique ID of the option.
|
||||
*/
|
||||
public set id(newId: string) {
|
||||
this._id = newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this option by attempting to read its value from a cache file.
|
||||
* If found, sets the current value. Also sets up a subscription to write updates back.
|
||||
*
|
||||
* @param cacheFile - The path to the cache file.
|
||||
*/
|
||||
public init(cacheFile: string): void {
|
||||
const rawData = readFile(cacheFile);
|
||||
|
||||
let cacheData: Record<string, unknown> = {};
|
||||
|
||||
if (rawData && rawData.trim() !== '') {
|
||||
try {
|
||||
cacheData = JSON.parse(rawData) as Record<string, unknown>;
|
||||
} catch {
|
||||
// do nuffin
|
||||
}
|
||||
}
|
||||
|
||||
const cachedVariable = cacheData[this._id];
|
||||
|
||||
if (cachedVariable !== undefined) {
|
||||
this.set(cachedVariable as T);
|
||||
}
|
||||
|
||||
this.subscribe((newVal) => {
|
||||
const reRaw = readFile(cacheFile);
|
||||
let currentCache: Record<string, unknown> = {};
|
||||
if (reRaw && reRaw.trim() !== '') {
|
||||
try {
|
||||
currentCache = JSON.parse(reRaw) as Record<string, unknown>;
|
||||
} catch {
|
||||
// Do nuffin
|
||||
}
|
||||
}
|
||||
currentCache[this._id] = newVal;
|
||||
writeFile(cacheFile, JSON.stringify(currentCache, null, 2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this option by attempting to read its default value from the default file.
|
||||
* If found, sets the current value.
|
||||
*/
|
||||
public createDefault(): void {
|
||||
const rawData = readFile(defaultFile);
|
||||
|
||||
let defaultData: Record<string, unknown> = {};
|
||||
|
||||
if (rawData && rawData.trim() !== '') {
|
||||
try {
|
||||
defaultData = JSON.parse(rawData) as Record<string, unknown>;
|
||||
} catch {
|
||||
// do nuffin
|
||||
}
|
||||
}
|
||||
|
||||
const defaultVal = defaultData[this._id];
|
||||
|
||||
if (defaultVal !== undefined) {
|
||||
this.set(defaultVal as T);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the value of this option to its initial value if not persistent and if it differs from the current value.
|
||||
*
|
||||
* @returns Returns the option's ID if reset occurred, otherwise undefined.
|
||||
*/
|
||||
public reset(): string | undefined {
|
||||
if (this.persistent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const current = this.get();
|
||||
|
||||
if (JSON.stringify(current) !== JSON.stringify(this.initial)) {
|
||||
this.set(this.initial);
|
||||
return this._id;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an `Opt` instance with the given initial value and properties.
|
||||
* @template T
|
||||
* @param initial - The initial value.
|
||||
* @param [props] - Additional properties.
|
||||
*/
|
||||
export function opt<T>(initial: T, props?: OptProps): Opt<T> {
|
||||
return new Opt(initial, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverses the provided object to extract all `Opt` instances, assigning IDs to each.
|
||||
*
|
||||
* @param object - The object containing `Opt` instances.
|
||||
* @param [path=''] - The current path (used internally).
|
||||
* @param [arr=[]] - The accumulator array for found `Opt` instances.
|
||||
* @returns An array of all found `Opt` instances.
|
||||
*/
|
||||
function getOptions(object: Record<string, unknown>, path = '', arr: Opt[] = []): Opt[] {
|
||||
for (const key in object) {
|
||||
const value = object[key];
|
||||
const id = path ? `${path}.${key}` : key;
|
||||
|
||||
if (value instanceof Variable) {
|
||||
const optValue = value as Opt;
|
||||
optValue.id = id;
|
||||
arr.push(optValue);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
getOptions(value as Record<string, unknown>, id, arr);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes options from a given object structure. The returned object
|
||||
* includes methods to reset values, reset theme colors, and handle dependencies.
|
||||
*
|
||||
* @template T extends object
|
||||
* @param cacheFile - The file path to store cached values.
|
||||
* @param object - The object containing nested `Opt` instances.
|
||||
* @param [confFile='config.json'] - The configuration file name stored in TMP.
|
||||
* @returns The original object extended with additional methods for handling options.
|
||||
*/
|
||||
export function mkOptions<T extends object>(
|
||||
cacheFile: string,
|
||||
object: T,
|
||||
confFile: string = 'config.json',
|
||||
): T & MkOptionsResult {
|
||||
const allOptions = getOptions(object as Record<string, unknown>);
|
||||
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
allOptions[i].init(cacheFile);
|
||||
}
|
||||
|
||||
ensureDirectory(cacheFile.split('/').slice(0, -1).join('/'));
|
||||
ensureDirectory(defaultFile.split('/').slice(0, -1).join('/'));
|
||||
|
||||
const configFile = `${TMP}/${confFile}`;
|
||||
|
||||
const values: Record<string, unknown> = {};
|
||||
const defaultValues: Record<string, unknown> = {};
|
||||
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
const option = allOptions[i];
|
||||
const val = option.value;
|
||||
|
||||
values[option.id] = val;
|
||||
|
||||
if (isHexColor(val as string)) {
|
||||
defaultValues[option.id] = option.initial;
|
||||
} else {
|
||||
defaultValues[option.id] = val;
|
||||
}
|
||||
}
|
||||
|
||||
writeFile(defaultFile, JSON.stringify(defaultValues, null, 2));
|
||||
writeFile(configFile, JSON.stringify(values, null, 2));
|
||||
|
||||
monitorFile(configFile, () => {
|
||||
const raw = readFile(configFile);
|
||||
|
||||
if (!raw || raw.trim() === '') return;
|
||||
|
||||
let cache: Record<string, unknown>;
|
||||
|
||||
try {
|
||||
cache = JSON.parse(raw) as Record<string, unknown>;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
const opt = allOptions[i];
|
||||
const newVal = cache[opt.id];
|
||||
const oldVal = opt.get();
|
||||
|
||||
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
|
||||
opt.set(newVal as T);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A simple sleep utility.
|
||||
*
|
||||
* @param [ms=0] - Milliseconds to sleep.
|
||||
*/
|
||||
function sleep(ms = 0): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all options to their initial values if possible.
|
||||
*
|
||||
* @param opts - Array of all option instances.
|
||||
* @returns IDs of all reset options.
|
||||
*/
|
||||
async function resetAll(opts: Opt[]): Promise<string[]> {
|
||||
const results: string[] = [];
|
||||
for (let i = 0; i < opts.length; i++) {
|
||||
const id = opts[i].reset();
|
||||
|
||||
if (id) {
|
||||
results.push(id);
|
||||
await sleep(50);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
return Object.assign(object, {
|
||||
configFile,
|
||||
array: (): Opt[] => allOptions,
|
||||
async reset(): Promise<string> {
|
||||
const ids = await resetAll(allOptions);
|
||||
|
||||
return ids.join('\n');
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a callback that fires when any option whose ID starts with any of the given dependencies changes.
|
||||
*
|
||||
* @param deps - An array of dependency prefixes.
|
||||
* @param callback - The callback function to execute on changes.
|
||||
*/
|
||||
handler(deps: string[], callback: () => void): void {
|
||||
for (let i = 0; i < allOptions.length; i++) {
|
||||
const opt = allOptions[i];
|
||||
|
||||
for (let j = 0; j < deps.length; j++) {
|
||||
if (opt.id.startsWith(deps[j])) {
|
||||
opt.subscribe(callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
90
src/lib/poller/BashPoller.ts
Normal file
90
src/lib/poller/BashPoller.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Bind } from 'src/lib/types/variable';
|
||||
import { GenericFunction } from 'src/lib/types/customModules/generic';
|
||||
import { BarModule } from 'src/lib/types/options';
|
||||
import { Poller } from './Poller';
|
||||
import { execAsync, Variable } from 'astal';
|
||||
|
||||
/**
|
||||
* A class that manages polling of a variable by executing a bash command at specified intervals.
|
||||
*/
|
||||
export class BashPoller<Value, Parameters extends unknown[]> {
|
||||
private poller: Poller;
|
||||
|
||||
private params: Parameters;
|
||||
|
||||
/**
|
||||
* Creates an instance of BashPoller.
|
||||
*
|
||||
* @param targetVariable - The target variable to poll.
|
||||
* @param trackers - An array of trackers to monitor.
|
||||
* @param pollingInterval - The interval at which polling occurs.
|
||||
* @param updateCommand - The command to update the target variable.
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
* @param params - Additional parameters for the polling function.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* //##################### EXAMPLE ##########################
|
||||
* const updatesPoller = new BashPoller<string, []>(
|
||||
* pendingUpdates,
|
||||
* [bind(padZero), bind(postInputUpdater)],
|
||||
* bind(pollingInterval),
|
||||
* updateCommand.value,
|
||||
* processUpdateCount,
|
||||
* );
|
||||
* //#######################################################
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private targetVariable: Variable<Value>,
|
||||
private trackers: Bind[],
|
||||
private pollingInterval: Bind,
|
||||
private updateCommand: string,
|
||||
private pollingFunction: GenericFunction<Value, [string, ...Parameters]>,
|
||||
...params: Parameters
|
||||
) {
|
||||
this.params = params;
|
||||
|
||||
this.poller = new Poller(this.pollingInterval, this.trackers, this.execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the bash command specified in the updateCommand property.
|
||||
*
|
||||
* The result of the command is processed by the pollingFunction and
|
||||
* assigned to the targetVariable.
|
||||
*/
|
||||
public execute = async (): Promise<void> => {
|
||||
try {
|
||||
const res = await execAsync(`bash -c "${this.updateCommand}"`);
|
||||
this.targetVariable.set(await this.pollingFunction(res, ...this.params));
|
||||
} catch (error) {
|
||||
console.error(`Error executing bash command "${this.updateCommand}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the polling process.
|
||||
*/
|
||||
public start(): void {
|
||||
this.poller.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the polling process.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.poller.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the poller with the specified module.
|
||||
*
|
||||
* @param moduleName - The name of the module to initialize.
|
||||
*/
|
||||
public initialize(moduleName?: BarModule): void {
|
||||
this.poller.initialize(moduleName);
|
||||
}
|
||||
}
|
||||
86
src/lib/poller/FunctionPoller.ts
Normal file
86
src/lib/poller/FunctionPoller.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Bind } from 'src/lib/types/variable';
|
||||
import { GenericFunction } from 'src/lib/types/customModules/generic';
|
||||
import { BarModule } from 'src/lib/types/options';
|
||||
import { Poller } from './Poller';
|
||||
import { Variable } from 'astal';
|
||||
|
||||
/**
|
||||
* A class that manages polling of a variable by executing a generic function at specified intervals.
|
||||
*/
|
||||
export class FunctionPoller<Value, Parameters extends unknown[] = []> {
|
||||
private poller: Poller;
|
||||
|
||||
private params: Parameters;
|
||||
|
||||
/**
|
||||
* Creates an instance of FunctionPoller.
|
||||
*
|
||||
* @param targetVariable - The target variable to poll.
|
||||
* @param trackers - An array of trackers to monitor.
|
||||
* @param pollingInterval - The interval at which polling occurs.
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
* @param params - Additional parameters for the polling function.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* //##################### EXAMPLE ##########################
|
||||
* const cpuPoller = new FunctionPoller<number, []>(
|
||||
* cpuUsage,
|
||||
* [bind(round)],
|
||||
* bind(pollingInterval),
|
||||
* computeCPU,
|
||||
* );
|
||||
* //#######################################################
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private targetVariable: Variable<Value>,
|
||||
private trackers: Bind[],
|
||||
private pollingInterval: Bind,
|
||||
private pollingFunction: GenericFunction<Value, Parameters>,
|
||||
...params: Parameters
|
||||
) {
|
||||
this.params = params;
|
||||
|
||||
this.poller = new Poller(this.pollingInterval, this.trackers, this.execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the polling function with the provided parameters.
|
||||
*
|
||||
* The result of the function is assigned to the target variable.
|
||||
*/
|
||||
private execute = async (): Promise<void> => {
|
||||
try {
|
||||
const result = await this.pollingFunction(...this.params);
|
||||
this.targetVariable.set(result);
|
||||
} catch (error) {
|
||||
console.error('Error executing polling function:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the polling process.
|
||||
*/
|
||||
public start(): void {
|
||||
this.poller.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the polling process.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.poller.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the poller with the specified module.
|
||||
*
|
||||
* @param moduleName - The name of the module to initialize.
|
||||
*/
|
||||
public initialize(moduleName?: BarModule): void {
|
||||
this.poller.initialize(moduleName);
|
||||
}
|
||||
}
|
||||
107
src/lib/poller/Poller.ts
Normal file
107
src/lib/poller/Poller.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Bind } from 'src/lib/types/variable';
|
||||
import { BarModule } from 'src/lib/types/options';
|
||||
import { getLayoutItems } from 'src/lib/utils';
|
||||
import { AstalIO, interval, Variable } from 'astal';
|
||||
|
||||
const { layouts } = options.bar;
|
||||
|
||||
/**
|
||||
* A class that manages the polling lifecycle, including interval management and execution state.
|
||||
*/
|
||||
export class Poller {
|
||||
private intervalInstance: AstalIO.Time | null = null;
|
||||
private isExecuting: boolean = false;
|
||||
private pollingFunction: () => Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates an instance of Poller.
|
||||
* @param pollingInterval - The interval at which polling occurs.
|
||||
* @param trackers - An array of trackers to monitor.
|
||||
* @param pollingFunction - The function to execute during each poll.
|
||||
*/
|
||||
constructor(
|
||||
private pollingInterval: Bind,
|
||||
private trackers: Bind[],
|
||||
pollingFunction: () => Promise<void>,
|
||||
) {
|
||||
this.pollingFunction = pollingFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the polling process by setting up the interval.
|
||||
*/
|
||||
public start(): void {
|
||||
Variable.derive([this.pollingInterval, ...this.trackers], (intervalMs: number) => {
|
||||
this.executePolling(intervalMs);
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the polling process and cleans up resources.
|
||||
*/
|
||||
public stop(): void {
|
||||
if (this.intervalInstance !== null) {
|
||||
this.intervalInstance.cancel();
|
||||
this.intervalInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the polling based on module usage.
|
||||
*
|
||||
* If not module is provided then we can safely assume that we want
|
||||
* to always run the pollig interval.
|
||||
*
|
||||
* @param moduleName - The name of the module to initialize.
|
||||
*/
|
||||
public initialize(moduleName?: BarModule): void {
|
||||
if (moduleName === undefined) {
|
||||
return this.start();
|
||||
}
|
||||
|
||||
const initialModules = getLayoutItems();
|
||||
|
||||
if (initialModules.includes(moduleName)) {
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
layouts.subscribe(() => {
|
||||
const usedModules = getLayoutItems();
|
||||
|
||||
if (usedModules.includes(moduleName)) {
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the polling function at the specified interval.
|
||||
*
|
||||
* @param intervalMs - The polling interval in milliseconds.
|
||||
*/
|
||||
private executePolling(intervalMs: number): void {
|
||||
if (this.intervalInstance !== null) {
|
||||
this.intervalInstance.cancel();
|
||||
}
|
||||
|
||||
this.intervalInstance = interval(intervalMs, async () => {
|
||||
if (this.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isExecuting = true;
|
||||
|
||||
try {
|
||||
await this.pollingFunction();
|
||||
} catch (error) {
|
||||
console.error('Error during polling execution:', error);
|
||||
} finally {
|
||||
this.isExecuting = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
24
src/lib/session.ts
Normal file
24
src/lib/session.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { App } from 'astal/gtk3';
|
||||
import { Gio } from 'astal/file';
|
||||
import { GLib } from 'astal/gobject';
|
||||
|
||||
declare global {
|
||||
const CONFIG: string;
|
||||
const TMP: string;
|
||||
const USER: string;
|
||||
const SRC_DIR: string;
|
||||
}
|
||||
|
||||
export function ensureDirectory(path: string): void {
|
||||
if (!GLib.file_test(path, GLib.FileTest.EXISTS)) Gio.File.new_for_path(path).make_directory_with_parents(null);
|
||||
}
|
||||
|
||||
Object.assign(globalThis, {
|
||||
CONFIG: `${GLib.get_user_config_dir()}/hyprpanel/config.json`,
|
||||
TMP: `${GLib.get_tmp_dir()}/hyprpanel`,
|
||||
USER: GLib.get_user_name(),
|
||||
SRC_DIR: GLib.getenv('HYPRPANEL_DATADIR') ?? SRC,
|
||||
});
|
||||
|
||||
ensureDirectory(TMP);
|
||||
App.add_icons(`${SRC_DIR}/assets`);
|
||||
95
src/lib/shared/eventHandlers.ts
Normal file
95
src/lib/shared/eventHandlers.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { GtkWidget } from 'src/lib/types/widget.js';
|
||||
import { Gdk } from 'astal/gtk3';
|
||||
import { ThrottleFn } from '../types/utils';
|
||||
|
||||
/**
|
||||
* Connects a primary click handler and returns a disconnect function.
|
||||
*/
|
||||
export function onPrimaryClick(widget: GtkWidget, handler: (self: GtkWidget, event: Gdk.Event) => void): () => void {
|
||||
const id = widget.connect('button-press-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const eventButton = event.get_button()[1];
|
||||
if (eventButton === Gdk.BUTTON_PRIMARY) {
|
||||
handler(self, event);
|
||||
}
|
||||
});
|
||||
return () => widget.disconnect(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a secondary click handler and returns a disconnect function.
|
||||
*/
|
||||
export function onSecondaryClick(widget: GtkWidget, handler: (self: GtkWidget, event: Gdk.Event) => void): () => void {
|
||||
const id = widget.connect('button-press-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const eventButton = event.get_button()[1];
|
||||
if (eventButton === Gdk.BUTTON_SECONDARY) {
|
||||
handler(self, event);
|
||||
}
|
||||
});
|
||||
return () => widget.disconnect(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a middle click handler and returns a disconnect function.
|
||||
*/
|
||||
export function onMiddleClick(widget: GtkWidget, handler: (self: GtkWidget, event: Gdk.Event) => void): () => void {
|
||||
const id = widget.connect('button-press-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const eventButton = event.get_button()[1];
|
||||
if (eventButton === Gdk.BUTTON_MIDDLE) {
|
||||
handler(self, event);
|
||||
}
|
||||
});
|
||||
return () => widget.disconnect(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a scroll handler and returns a disconnect function.
|
||||
*/
|
||||
export function onScroll(
|
||||
widget: GtkWidget,
|
||||
throttledHandler: ThrottleFn,
|
||||
scrollUpAction: string,
|
||||
scrollDownAction: string,
|
||||
): () => void {
|
||||
const id = widget.connect('scroll-event', (self: GtkWidget, event: Gdk.Event) => {
|
||||
const [directionSuccess, direction] = event.get_scroll_direction();
|
||||
const [deltasSuccess, , yScroll] = event.get_scroll_deltas();
|
||||
|
||||
if (directionSuccess) {
|
||||
handleScrollDirection(direction, scrollUpAction, scrollDownAction, self, event, throttledHandler);
|
||||
} else if (deltasSuccess) {
|
||||
handleScrollDeltas(yScroll, scrollUpAction, scrollDownAction, self, event, throttledHandler);
|
||||
}
|
||||
});
|
||||
|
||||
return () => widget.disconnect(id);
|
||||
}
|
||||
|
||||
function handleScrollDirection(
|
||||
direction: Gdk.ScrollDirection,
|
||||
scrollUpAction: string,
|
||||
scrollDownAction: string,
|
||||
self: GtkWidget,
|
||||
event: Gdk.Event,
|
||||
throttledHandler: ThrottleFn,
|
||||
): void {
|
||||
if (direction === Gdk.ScrollDirection.UP) {
|
||||
throttledHandler(scrollUpAction, { clicked: self, event });
|
||||
} else if (direction === Gdk.ScrollDirection.DOWN) {
|
||||
throttledHandler(scrollDownAction, { clicked: self, event });
|
||||
}
|
||||
}
|
||||
|
||||
function handleScrollDeltas(
|
||||
yScroll: number,
|
||||
scrollUpAction: string,
|
||||
scrollDownAction: string,
|
||||
self: GtkWidget,
|
||||
event: Gdk.Event,
|
||||
throttledHandler: ThrottleFn,
|
||||
): void {
|
||||
if (yScroll > 0) {
|
||||
throttledHandler(scrollDownAction, { clicked: self, event });
|
||||
} else if (yScroll < 0) {
|
||||
throttledHandler(scrollUpAction, { clicked: self, event });
|
||||
}
|
||||
}
|
||||
47
src/lib/shared/hookHandler.ts
Normal file
47
src/lib/shared/hookHandler.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Connectable, Subscribable } from 'astal/binding';
|
||||
import { Widget } from 'astal/gtk3';
|
||||
|
||||
/**
|
||||
* A generic hook utility to manage setup and teardown based on dependencies.
|
||||
*
|
||||
* @param widget - The GtkWidget instance.
|
||||
* @param hookTarget - The object to hook into (Connectable or Subscribable).
|
||||
* @param setup - The setup function to execute, which returns a disconnect function.
|
||||
* @param signal - (Optional) The signal name if hooking into a Connectable.
|
||||
*/
|
||||
export function useHook(
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
widget: any,
|
||||
hookTarget: Connectable | Subscribable,
|
||||
setup: (() => void) | (() => () => void),
|
||||
signal?: string,
|
||||
): void {
|
||||
const passedWidget: Widget.Box = widget;
|
||||
let currentDisconnect: () => void = () => {};
|
||||
|
||||
const executeSetup = (): void => {
|
||||
currentDisconnect();
|
||||
if (typeof setup === 'function') {
|
||||
currentDisconnect = setup() || ((): void => {});
|
||||
}
|
||||
};
|
||||
|
||||
const isConnectable = (target: Connectable | Subscribable): target is Connectable => {
|
||||
return 'connect' in target;
|
||||
};
|
||||
|
||||
const isSubscribable = (target: Connectable | Subscribable): target is Subscribable => {
|
||||
return 'subscribe' in target;
|
||||
};
|
||||
|
||||
const hookIntoTarget = (): void => {
|
||||
if (signal && isConnectable(hookTarget)) {
|
||||
passedWidget.hook(hookTarget, signal, executeSetup);
|
||||
} else if (isSubscribable(hookTarget)) {
|
||||
passedWidget.hook(hookTarget, executeSetup);
|
||||
}
|
||||
};
|
||||
|
||||
executeSetup();
|
||||
hookIntoTarget();
|
||||
}
|
||||
33
src/lib/shared/media.ts
Normal file
33
src/lib/shared/media.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import { mprisService } from '../constants/services';
|
||||
|
||||
export const getCurrentPlayer = (
|
||||
activePlayer: AstalMpris.Player = mprisService.get_players()[0],
|
||||
): AstalMpris.Player => {
|
||||
const statusOrder = {
|
||||
[AstalMpris.PlaybackStatus.PLAYING]: 1,
|
||||
[AstalMpris.PlaybackStatus.PAUSED]: 2,
|
||||
[AstalMpris.PlaybackStatus.STOPPED]: 3,
|
||||
};
|
||||
|
||||
const mprisPlayers = mprisService.get_players();
|
||||
if (mprisPlayers.length === 0) {
|
||||
return mprisPlayers[0];
|
||||
}
|
||||
|
||||
const isPlaying = mprisPlayers.some(
|
||||
(p: AstalMpris.Player) => p.playbackStatus === AstalMpris.PlaybackStatus.PLAYING,
|
||||
);
|
||||
|
||||
const playerStillExists = mprisPlayers.some((p) => activePlayer.bus_name === p.bus_name);
|
||||
|
||||
const nextPlayerUp = mprisPlayers.sort(
|
||||
(a: AstalMpris.Player, b: AstalMpris.Player) => statusOrder[a.playbackStatus] - statusOrder[b.playbackStatus],
|
||||
)[0];
|
||||
|
||||
if (isPlaying || !playerStillExists) {
|
||||
return nextPlayerUp;
|
||||
}
|
||||
|
||||
return activePlayer;
|
||||
};
|
||||
21
src/lib/shared/notifications.ts
Normal file
21
src/lib/shared/notifications.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import AstalNotifd from 'gi://AstalNotifd?version=0.1';
|
||||
|
||||
const normalizeName = (name: string): string => name.toLowerCase().replace(/\s+/g, '_');
|
||||
|
||||
export const isNotificationIgnored = (notification: AstalNotifd.Notification, filter: string[]): boolean => {
|
||||
const notificationFilters = new Set(filter.map(normalizeName));
|
||||
const normalizedAppName = normalizeName(notification.app_name);
|
||||
|
||||
return notificationFilters.has(normalizedAppName);
|
||||
};
|
||||
|
||||
export const filterNotifications = (
|
||||
notifications: AstalNotifd.Notification[],
|
||||
filter: string[],
|
||||
): AstalNotifd.Notification[] => {
|
||||
const filteredNotifications = notifications.filter((notif: AstalNotifd.Notification) => {
|
||||
return !isNotificationIgnored(notif, filter);
|
||||
});
|
||||
|
||||
return filteredNotifications;
|
||||
};
|
||||
11
src/lib/types/astal-extensions.d.ts
vendored
Normal file
11
src/lib/types/astal-extensions.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Gdk } from 'astal/gtk3';
|
||||
|
||||
declare module 'astal/gtk3' {
|
||||
interface EventButton extends Gdk.Event {
|
||||
get_root_coords(): [number, number];
|
||||
}
|
||||
|
||||
interface EventScroll extends Gdk.Event {
|
||||
direction: Gdk.ScrollDirection;
|
||||
}
|
||||
}
|
||||
14
src/lib/types/audio.d.ts
vendored
Normal file
14
src/lib/types/audio.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export type InputDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||
|
||||
type DummyDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||
type RealPlaybackDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
|
||||
export type PlaybackDevices = DummyDevices | RealPlaybackDevices;
|
||||
|
||||
export type MediaTags = {
|
||||
title: string;
|
||||
artists: string;
|
||||
artist: string;
|
||||
album: string;
|
||||
name: string;
|
||||
identity: string;
|
||||
};
|
||||
42
src/lib/types/bar.d.ts
vendored
Normal file
42
src/lib/types/bar.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Binding, Connectable } from 'types/service';
|
||||
import { Variable } from 'types/variable';
|
||||
import Box from 'types/widgets/box';
|
||||
import Button, { ButtonProps } from 'types/widgets/button';
|
||||
import Label from 'types/widgets/label';
|
||||
import { Attribute, Child } from './widget';
|
||||
import { Widget } from 'astal/gtk3';
|
||||
|
||||
export type BarBoxChild = {
|
||||
component: JSX.Element;
|
||||
isVisible?: boolean;
|
||||
isVis?: Variable<boolean>;
|
||||
isBox?: boolean;
|
||||
boxClass: string;
|
||||
tooltip_text?: string | Binding<string>;
|
||||
} & ({ isBox: true; props: Widget.BoxProps } | { isBox?: false; props: Widget.ButtonProps });
|
||||
|
||||
export type SelfButton = Button<Child, Attribute>;
|
||||
|
||||
export type BoxHook = (self: Box<Gtk.Widget, Gtk.Widget>) => void;
|
||||
export type LabelHook = (self: Label<Gtk.Widget>) => void;
|
||||
|
||||
export type BarModule = {
|
||||
icon?: string | Binding<string>;
|
||||
textIcon?: string | Binding<string>;
|
||||
useTextIcon?: Binding<boolean>;
|
||||
label?: string | Binding<string>;
|
||||
labelHook?: LabelHook;
|
||||
boundLabel?: string;
|
||||
tooltipText?: string | Binding<string>;
|
||||
boxClass: string;
|
||||
props?: Widget.ButtonProps;
|
||||
showLabel?: boolean;
|
||||
showLabelBinding?: Binding;
|
||||
hook?: BoxHook;
|
||||
connection?: Binding<Connectable>;
|
||||
};
|
||||
|
||||
export type ResourceLabelType = 'used/total' | 'used' | 'percentage' | 'free';
|
||||
|
||||
export type NetstatLabelType = 'full' | 'in' | 'out';
|
||||
export type RateUnit = 'GiB' | 'MiB' | 'KiB' | 'auto';
|
||||
5
src/lib/types/battery.ts
Normal file
5
src/lib/types/battery.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type BatteryIconKeys = 0 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100;
|
||||
|
||||
export type BatteryIcons = {
|
||||
[key in BatteryIconKeys]: string;
|
||||
};
|
||||
20
src/lib/types/customModules/generic.d.ts
vendored
Normal file
20
src/lib/types/customModules/generic.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export type GenericFunction<Value, Parameters extends unknown[]> = (...args: Parameters) => Promise<Value> | Value;
|
||||
|
||||
export type GenericResourceMetrics = {
|
||||
total: number;
|
||||
used: number;
|
||||
percentage: number;
|
||||
};
|
||||
|
||||
export type GenericResourceData = GenericResourceMetrics & {
|
||||
free: number;
|
||||
};
|
||||
|
||||
export type Postfix = 'TiB' | 'GiB' | 'MiB' | 'KiB' | 'B';
|
||||
|
||||
export type UpdateHandlers = {
|
||||
disconnectPrimary: () => void;
|
||||
disconnectSecondary: () => void;
|
||||
disconnectMiddle: () => void;
|
||||
disconnectScroll: () => void;
|
||||
};
|
||||
32
src/lib/types/customModules/kbLayout.d.ts
vendored
Normal file
32
src/lib/types/customModules/kbLayout.d.ts
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
import { layoutMap } from 'src/components/bar/modules/kblayout/layouts';
|
||||
|
||||
export type KbLabelType = 'layout' | 'code';
|
||||
|
||||
export type HyprctlKeyboard = {
|
||||
address: string;
|
||||
name: string;
|
||||
rules: string;
|
||||
model: string;
|
||||
layout: string;
|
||||
variant: string;
|
||||
options: string;
|
||||
active_keymap: string;
|
||||
main: boolean;
|
||||
};
|
||||
|
||||
export type HyprctlMouse = {
|
||||
address: string;
|
||||
name: string;
|
||||
defaultSpeed: number;
|
||||
};
|
||||
|
||||
export type HyprctlDeviceLayout = {
|
||||
mice: HyprctlMouse[];
|
||||
keyboards: HyprctlKeyboard[];
|
||||
tablets: unknown[];
|
||||
touch: unknown[];
|
||||
switches: unknown[];
|
||||
};
|
||||
|
||||
export type LayoutKeys = keyof typeof layoutMap;
|
||||
export type LayoutValues = (typeof layoutMap)[LayoutKeys];
|
||||
4
src/lib/types/customModules/network.d.ts
vendored
Normal file
4
src/lib/types/customModules/network.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export type NetworkResourceData = {
|
||||
in: string;
|
||||
out: string;
|
||||
};
|
||||
17
src/lib/types/customModules/utils.d.ts
vendored
Normal file
17
src/lib/types/customModules/utils.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Binding } from 'src/lib/utils';
|
||||
import { Variable } from 'types/variable';
|
||||
|
||||
export type InputHandlerEvents = {
|
||||
onPrimaryClick?: Binding;
|
||||
onSecondaryClick?: Binding;
|
||||
onMiddleClick?: Binding;
|
||||
onScrollUp?: Binding;
|
||||
onScrollDown?: Binding;
|
||||
};
|
||||
|
||||
export type RunAsyncCommand = (
|
||||
cmd: string,
|
||||
args: EventArgs,
|
||||
fn?: (output: string) => void,
|
||||
postInputUpdater?: Variable<boolean>,
|
||||
) => void;
|
||||
15
src/lib/types/dashboard.d.ts
vendored
Normal file
15
src/lib/types/dashboard.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export type ShortcutFixed = {
|
||||
tooltip: string;
|
||||
command: string;
|
||||
icon: string;
|
||||
configurable: false;
|
||||
};
|
||||
|
||||
export type ShortcutVariable = {
|
||||
tooltip: VarType<string>;
|
||||
command: VarType<string>;
|
||||
icon: VarType<string>;
|
||||
configurable?: true;
|
||||
};
|
||||
|
||||
export type Shortcut = ShortcutFixed | ShortcutVariable;
|
||||
15
src/lib/types/defaults/bar.ts
Normal file
15
src/lib/types/defaults/bar.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import { NetstatLabelType, ResourceLabelType } from '../bar';
|
||||
import { BarLocation } from '../options';
|
||||
|
||||
export const LABEL_TYPES: ResourceLabelType[] = ['used/total', 'used', 'free', 'percentage'];
|
||||
|
||||
export const NETWORK_LABEL_TYPES: NetstatLabelType[] = ['full', 'in', 'out'];
|
||||
|
||||
type LocationMap = {
|
||||
[key in BarLocation]: Astal.WindowAnchor;
|
||||
};
|
||||
export const locationMap: LocationMap = {
|
||||
top: Astal.WindowAnchor.TOP,
|
||||
bottom: Astal.WindowAnchor.BOTTOM,
|
||||
};
|
||||
10
src/lib/types/defaults/netstat.ts
Normal file
10
src/lib/types/defaults/netstat.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { RateUnit } from '../bar';
|
||||
import { NetworkResourceData } from '../customModules/network';
|
||||
|
||||
export const GET_DEFAULT_NETSTAT_DATA = (dataType: RateUnit): NetworkResourceData => {
|
||||
if (dataType === 'auto') {
|
||||
return { in: `0 Kib/s`, out: `0 Kib/s` };
|
||||
}
|
||||
|
||||
return { in: `0 ${dataType}/s`, out: `0 ${dataType}/s` };
|
||||
};
|
||||
60
src/lib/types/defaults/options.ts
Normal file
60
src/lib/types/defaults/options.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
export const defaultColorMap = {
|
||||
rosewater: '#f5e0dc',
|
||||
flamingo: '#f2cdcd',
|
||||
pink: '#f5c2e7',
|
||||
mauve: '#cba6f7',
|
||||
red: '#f38ba8',
|
||||
maroon: '#eba0ac',
|
||||
peach: '#fab387',
|
||||
yellow: '#f9e2af',
|
||||
green: '#a6e3a1',
|
||||
teal: '#94e2d5',
|
||||
sky: '#89dceb',
|
||||
sapphire: '#74c7ec',
|
||||
blue: '#89b4fa',
|
||||
lavender: '#b4befe',
|
||||
text: '#cdd6f4',
|
||||
subtext1: '#bac2de',
|
||||
subtext2: '#a6adc8',
|
||||
overlay2: '#9399b2',
|
||||
overlay1: '#7f849c',
|
||||
overlay0: '#6c7086',
|
||||
surface2: '#585b70',
|
||||
surface1: '#45475a',
|
||||
surface0: '#313244',
|
||||
base2: '#242438',
|
||||
base: '#1e1e2e',
|
||||
mantle: '#181825',
|
||||
crust: '#11111b',
|
||||
surface1_2: '#454759',
|
||||
text2: '#cdd6f3',
|
||||
pink2: '#f5c2e6',
|
||||
red2: '#f38ba7',
|
||||
peach2: '#fab386',
|
||||
mantle2: '#181824',
|
||||
surface0_2: '#313243',
|
||||
surface2_2: '#585b69',
|
||||
overlay1_2: '#7f849b',
|
||||
lavender2: '#b4befd',
|
||||
mauve2: '#cba6f6',
|
||||
green2: '#a6e3a0',
|
||||
sky2: '#89dcea',
|
||||
teal2: '#94e2d4',
|
||||
yellow2: '#f9e2ad',
|
||||
maroon2: '#eba0ab',
|
||||
crust2: '#11111a',
|
||||
pink3: '#f5c2e8',
|
||||
red3: '#f38ba9',
|
||||
mantle3: '#181826',
|
||||
surface0_3: '#313245',
|
||||
surface2_3: '#585b71',
|
||||
overlay1_3: '#7f849d',
|
||||
lavender3: '#b4beff',
|
||||
mauve3: '#cba6f8',
|
||||
green3: '#a6e3a2',
|
||||
sky3: '#89dcec',
|
||||
teal3: '#94e2d6',
|
||||
yellow3: '#f9e2ae',
|
||||
maroon3: '#eba0ad',
|
||||
crust3: '#11111c',
|
||||
} as const;
|
||||
1053
src/lib/types/defaults/weather.ts
Normal file
1053
src/lib/types/defaults/weather.ts
Normal file
File diff suppressed because it is too large
Load Diff
15
src/lib/types/dropdownmenu.d.ts
vendored
Normal file
15
src/lib/types/dropdownmenu.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { GtkWidget, Transition } from './widget';
|
||||
import { Astal, Gtk } from 'astal/gtk3';
|
||||
import { WindowProps } from 'astal/gtk3/widget';
|
||||
import { Opt } from '../option';
|
||||
import { Binding } from 'astal';
|
||||
import { BindableChild } from 'astal/gtk3/astalify';
|
||||
|
||||
export interface DropdownMenuProps extends WindowProps {
|
||||
name: string;
|
||||
child?: BindableChild | BindableChild[];
|
||||
layout?: string;
|
||||
transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
|
||||
exclusivity?: Astal.Exclusivity;
|
||||
fixed?: boolean;
|
||||
}
|
||||
3
src/lib/types/filechooser.d.ts
vendored
Normal file
3
src/lib/types/filechooser.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export type Config = {
|
||||
[key: string]: string | number | boolean | object;
|
||||
};
|
||||
4
src/lib/types/globals.d.ts
vendored
Normal file
4
src/lib/types/globals.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export type MousePos = {
|
||||
source: string;
|
||||
pos: number[];
|
||||
};
|
||||
25
src/lib/types/gpustat.d.ts
vendored
Normal file
25
src/lib/types/gpustat.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
export type GPUStatProcess = {
|
||||
username: string;
|
||||
command: string;
|
||||
full_command: string[];
|
||||
gpu_memory_usage: number;
|
||||
cpu_percent: number;
|
||||
cpu_memory_usage: number;
|
||||
pid: number;
|
||||
};
|
||||
|
||||
export type GPUStat = {
|
||||
index: number;
|
||||
uuid: string;
|
||||
name: string;
|
||||
'temperature.gpu': number;
|
||||
'fan.speed': number;
|
||||
'utilization.gpu': number;
|
||||
'utilization.enc': number;
|
||||
'utilization.dec': number;
|
||||
'power.draw': number;
|
||||
'enforced.power.limit': number;
|
||||
'memory.used': number;
|
||||
'memory.total': number;
|
||||
processes: Process[];
|
||||
};
|
||||
6
src/lib/types/mpris.d.ts
vendored
Normal file
6
src/lib/types/mpris.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import AstalMpris from 'gi://AstalMpris?version=0.1';
|
||||
import icons2 from '../icons/icons2';
|
||||
|
||||
export type PlaybackIconMap = {
|
||||
[key in AstalMpris.PlaybackStatus]: string;
|
||||
};
|
||||
16
src/lib/types/network.d.ts
vendored
Normal file
16
src/lib/types/network.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { WIFI_STATUS_MAP } from 'src/globals/network';
|
||||
|
||||
export type AccessPoint = {
|
||||
bssid: string | null;
|
||||
address: string | null;
|
||||
lastSeen: number;
|
||||
ssid: string | null;
|
||||
active: boolean;
|
||||
strength: number;
|
||||
frequency: number;
|
||||
iconName: string | undefined;
|
||||
};
|
||||
|
||||
export type WifiStatus = keyof typeof WIFI_STATUS_MAP;
|
||||
|
||||
export type WifiIcon = '' | '' | '' | '' | '' | '' | '' | '' | '' | '' | '';
|
||||
15
src/lib/types/notification.d.ts
vendored
Normal file
15
src/lib/types/notification.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import icons from 'src/lib/icons/icons2';
|
||||
|
||||
export interface NotificationArgs {
|
||||
appName?: string;
|
||||
body?: string;
|
||||
iconName?: string;
|
||||
id?: number;
|
||||
summary?: string;
|
||||
urgency?: Urgency;
|
||||
category?: string;
|
||||
timeout?: number;
|
||||
transient?: boolean;
|
||||
}
|
||||
|
||||
export type NotificationIcon = keyof typeof icons.notifications;
|
||||
268
src/lib/types/options.d.ts
vendored
Normal file
268
src/lib/types/options.d.ts
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
import { Opt } from 'src/lib/option';
|
||||
import { Variable } from 'types/variable';
|
||||
import { defaultColorMap } from './defaults/options';
|
||||
import { Astal } from 'astal/gtk3';
|
||||
import { dropdownMenuList } from '../constants/options';
|
||||
|
||||
export type MkOptionsResult = {
|
||||
configFile: string;
|
||||
array: () => Opt[];
|
||||
reset: () => Promise<string>;
|
||||
handler: (deps: string[], callback: () => void) => void;
|
||||
};
|
||||
|
||||
export type RecursiveOptionsObject = {
|
||||
[key: string]:
|
||||
| RecursiveOptionsObject
|
||||
| Opt<string>
|
||||
| Opt<number>
|
||||
| Opt<boolean>
|
||||
| Variable<string>
|
||||
| Variable<number>
|
||||
| Variable<boolean>;
|
||||
};
|
||||
|
||||
export type BarLocation = 'top' | 'bottom';
|
||||
export type AutoHide = 'never' | 'fullscreen' | 'single-window';
|
||||
export type BarModule =
|
||||
| 'battery'
|
||||
| 'dashboard'
|
||||
| 'workspaces'
|
||||
| 'windowtitle'
|
||||
| 'media'
|
||||
| 'notifications'
|
||||
| 'volume'
|
||||
| 'network'
|
||||
| 'bluetooth'
|
||||
| 'clock'
|
||||
| 'ram'
|
||||
| 'cpu'
|
||||
| 'cputemp'
|
||||
| 'storage'
|
||||
| 'netstat'
|
||||
| 'kbinput'
|
||||
| 'updates'
|
||||
| 'submap'
|
||||
| 'weather'
|
||||
| 'power'
|
||||
| 'systray'
|
||||
| 'hypridle'
|
||||
| 'hyprsunset';
|
||||
|
||||
export type BarLayout = {
|
||||
left: BarModule[];
|
||||
middle: BarModule[];
|
||||
right: BarModule[];
|
||||
};
|
||||
export type BarLayouts = {
|
||||
[key: string]: BarLayout;
|
||||
};
|
||||
|
||||
export type Unit = 'imperial' | 'metric';
|
||||
export type PowerOptions = 'sleep' | 'reboot' | 'logout' | 'shutdown';
|
||||
export type NotificationAnchor =
|
||||
| 'top'
|
||||
| 'top right'
|
||||
| 'top left'
|
||||
| 'bottom'
|
||||
| 'bottom right'
|
||||
| 'bottom left'
|
||||
| 'left'
|
||||
| 'right';
|
||||
export type OSDAnchor = 'top left' | 'top' | 'top right' | 'right' | 'bottom right' | 'bottom' | 'bottom left' | 'left';
|
||||
export type BarButtonStyles = 'default' | 'split' | 'wave' | 'wave2';
|
||||
|
||||
export type ThemeExportData = {
|
||||
filePath: string;
|
||||
themeOnly: boolean;
|
||||
};
|
||||
export type InputType =
|
||||
| 'number'
|
||||
| 'color'
|
||||
| 'float'
|
||||
| 'object'
|
||||
| 'string'
|
||||
| 'enum'
|
||||
| 'boolean'
|
||||
| 'img'
|
||||
| 'wallpaper'
|
||||
| 'export'
|
||||
| 'import'
|
||||
| 'config_import'
|
||||
| 'font';
|
||||
|
||||
export interface RowProps<T> {
|
||||
opt: Opt<T>;
|
||||
note?: string;
|
||||
type?: InputType;
|
||||
enums?: T[];
|
||||
max?: number;
|
||||
min?: number;
|
||||
disabledBinding?: Variable<boolean>;
|
||||
exportData?: ThemeExportData;
|
||||
subtitle?: string | VarType<string> | Opt;
|
||||
subtitleLink?: string;
|
||||
dependencies?: string[];
|
||||
increment?: number;
|
||||
}
|
||||
|
||||
export type OSDOrientation = 'horizontal' | 'vertical';
|
||||
|
||||
export type HexColor = `#${string}`;
|
||||
|
||||
export type WindowLayer = 'top' | 'bottom' | 'overlay' | 'background';
|
||||
|
||||
export type ActiveWsIndicator = 'underline' | 'highlight' | 'color';
|
||||
|
||||
export type MatugenColors = {
|
||||
background: HexColor;
|
||||
error: HexColor;
|
||||
error_container: HexColor;
|
||||
inverse_on_surface: HexColor;
|
||||
inverse_primary: HexColor;
|
||||
inverse_surface: HexColor;
|
||||
on_background: HexColor;
|
||||
on_error: HexColor;
|
||||
on_error_container: HexColor;
|
||||
on_primary: HexColor;
|
||||
on_primary_container: HexColor;
|
||||
on_primary_fixed: HexColor;
|
||||
on_primary_fixed_variant: HexColor;
|
||||
on_secondary: HexColor;
|
||||
on_secondary_container: HexColor;
|
||||
on_secondary_fixed: HexColor;
|
||||
on_secondary_fixed_variant: HexColor;
|
||||
on_surface: HexColor;
|
||||
on_surface_variant: HexColor;
|
||||
on_tertiary: HexColor;
|
||||
on_tertiary_container: HexColor;
|
||||
on_tertiary_fixed: HexColor;
|
||||
on_tertiary_fixed_variant: HexColor;
|
||||
outline: HexColor;
|
||||
outline_variant: HexColor;
|
||||
primary: HexColor;
|
||||
primary_container: HexColor;
|
||||
primary_fixed: HexColor;
|
||||
primary_fixed_dim: HexColor;
|
||||
scrim: HexColor;
|
||||
secondary: HexColor;
|
||||
secondary_container: HexColor;
|
||||
secondary_fixed: HexColor;
|
||||
secondary_fixed_dim: HexColor;
|
||||
shadow: HexColor;
|
||||
surface: HexColor;
|
||||
surface_bright: HexColor;
|
||||
surface_container: HexColor;
|
||||
surface_container_high: HexColor;
|
||||
surface_container_highest: HexColor;
|
||||
surface_container_low: HexColor;
|
||||
surface_container_lowest: HexColor;
|
||||
surface_dim: HexColor;
|
||||
surface_variant: HexColor;
|
||||
tertiary: HexColor;
|
||||
tertiary_container: HexColor;
|
||||
tertiary_fixed: HexColor;
|
||||
tertiary_fixed_dim: HexColor;
|
||||
};
|
||||
|
||||
export type MatugenVariation = {
|
||||
rosewater: HexColor;
|
||||
flamingo: HexColor;
|
||||
pink: HexColor;
|
||||
mauve: HexColor;
|
||||
red: HexColor;
|
||||
maroon: HexColor;
|
||||
peach: HexColor;
|
||||
yellow: HexColor;
|
||||
green: HexColor;
|
||||
teal: HexColor;
|
||||
sky: HexColor;
|
||||
sapphire: HexColor;
|
||||
blue: HexColor;
|
||||
lavender: HexColor;
|
||||
text: HexColor;
|
||||
subtext1: HexColor;
|
||||
subtext2: HexColor;
|
||||
overlay2: HexColor;
|
||||
overlay1: HexColor;
|
||||
overlay0: HexColor;
|
||||
surface2: HexColor;
|
||||
surface1: HexColor;
|
||||
surface0: HexColor;
|
||||
base2: HexColor;
|
||||
base: HexColor;
|
||||
mantle: HexColor;
|
||||
crust: HexColor;
|
||||
notifications_closer: HexColor;
|
||||
notifications_background: HexColor;
|
||||
dashboard_btn_text: HexColor;
|
||||
red2: HexColor;
|
||||
peach2: HexColor;
|
||||
pink2: HexColor;
|
||||
mantle2: HexColor;
|
||||
surface1_2: HexColor;
|
||||
surface0_2: HexColor;
|
||||
overlay1_2: HexColor;
|
||||
text2: HexColor;
|
||||
lavender2: HexColor;
|
||||
crust2: HexColor;
|
||||
maroon2: HexColor;
|
||||
mauve2: HexColor;
|
||||
green2: HexColor;
|
||||
surface2_2: HexColor;
|
||||
sky2: HexColor;
|
||||
teal2: HexColor;
|
||||
yellow2: HexColor;
|
||||
pink3: HexColor;
|
||||
red3: HexColor;
|
||||
mantle3: HexColor;
|
||||
surface0_3: HexColor;
|
||||
surface2_3: HexColor;
|
||||
overlay1_3: HexColor;
|
||||
lavender3: HexColor;
|
||||
mauve3: HexColor;
|
||||
green3: HexColor;
|
||||
sky3: HexColor;
|
||||
teal3: HexColor;
|
||||
yellow3: HexColor;
|
||||
maroon3: HexColor;
|
||||
crust3: HexColor;
|
||||
notifications_closer?: HexColor;
|
||||
notifications_background?: HexColor;
|
||||
dashboard_btn_text?: HexColor;
|
||||
};
|
||||
export type MatugenScheme =
|
||||
| 'content'
|
||||
| 'expressive'
|
||||
| 'fidelity'
|
||||
| 'fruit-salad'
|
||||
| 'monochrome'
|
||||
| 'neutral'
|
||||
| 'rainbow'
|
||||
| 'tonal-spot';
|
||||
|
||||
export type MatugenVariations =
|
||||
| 'standard_1'
|
||||
| 'standard_2'
|
||||
| 'standard_3'
|
||||
| 'monochrome_1'
|
||||
| 'monochrome_2'
|
||||
| 'monochrome_3'
|
||||
| 'vivid_1'
|
||||
| 'vivid_2'
|
||||
| 'vivid_3';
|
||||
|
||||
type MatugenTheme = 'light' | 'dark';
|
||||
|
||||
export type ColorMapKey = keyof typeof defaultColorMap;
|
||||
export type ColorMapValue = (typeof defaultColorMap)[ColorMapKey];
|
||||
|
||||
export type ScalingPriority = 'gdk' | 'hyprland' | 'both';
|
||||
|
||||
export type BluetoothBatteryState = 'paired' | 'connected' | 'always';
|
||||
|
||||
export type BorderLocation = 'none' | 'top' | 'right' | 'bottom' | 'left' | 'horizontal' | 'vertical' | 'full';
|
||||
|
||||
export type PositionAnchor = { [key: string]: Astal.WindowAnchor };
|
||||
|
||||
export type DropdownMenuList = (typeof dropdownMenuList)[number];
|
||||
52
src/lib/types/popupwindow.d.ts
vendored
Normal file
52
src/lib/types/popupwindow.d.ts
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Widget } from 'types/widgets/widget';
|
||||
import { WindowProps } from 'types/widgets/window';
|
||||
import { Transition } from './widget';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
|
||||
export type PopupWindowProps = {
|
||||
name: string;
|
||||
child?: BindableChild | BindableChild[];
|
||||
layout?: Layouts;
|
||||
transition?: Transition | Binding<Transition>;
|
||||
exclusivity?: Exclusivity;
|
||||
} & WindowProps;
|
||||
|
||||
export type LayoutFunction = (
|
||||
name: string,
|
||||
child: Widget,
|
||||
transition: Gtk.RevealerTransitionType,
|
||||
) => {
|
||||
center: () => Widget;
|
||||
top: () => Widget;
|
||||
'top-right': () => Widget;
|
||||
'top-center': () => Widget;
|
||||
'top-left': () => Widget;
|
||||
'bottom-left': () => Widget;
|
||||
'bottom-center': () => Widget;
|
||||
'bottom-right': () => Widget;
|
||||
};
|
||||
export type Layouts =
|
||||
| 'center'
|
||||
| 'top'
|
||||
| 'top-right'
|
||||
| 'top-center'
|
||||
| 'top-left'
|
||||
| 'bottom-left'
|
||||
| 'bottom-center'
|
||||
| 'bottom-right';
|
||||
|
||||
export type Opts = {
|
||||
className: string;
|
||||
vexpand: boolean;
|
||||
};
|
||||
|
||||
export type PaddingProps = {
|
||||
name: string;
|
||||
opts?: Opts;
|
||||
};
|
||||
|
||||
export type PopupRevealerProps = {
|
||||
name: string;
|
||||
child: GtkWidget;
|
||||
transition: Gtk.RevealerTransitionType;
|
||||
};
|
||||
1
src/lib/types/power.d.ts
vendored
Normal file
1
src/lib/types/power.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type Action = 'sleep' | 'reboot' | 'logout' | 'shutdown';
|
||||
9
src/lib/types/powerprofiles.d.ts
vendored
Normal file
9
src/lib/types/powerprofiles.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import PowerProfiles from 'types/service/powerprofiles.js';
|
||||
|
||||
export type PowerProfiles = InstanceType<typeof PowerProfiles>;
|
||||
export type PowerProfile = 'power-saver' | 'balanced' | 'performance';
|
||||
export type PowerProfileObject = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type ProfileType = 'balanced' | 'power-saver' | 'performance';
|
||||
6
src/lib/types/systray.d.ts
vendored
Normal file
6
src/lib/types/systray.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export type SystrayIconMap = {
|
||||
[key: string]: {
|
||||
icon: string;
|
||||
color: string;
|
||||
};
|
||||
};
|
||||
11
src/lib/types/utils.d.ts
vendored
Normal file
11
src/lib/types/utils.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { EventArgs } from './eventArgs';
|
||||
import { Variable } from 'types/variable';
|
||||
|
||||
export type ThrottleFn = (
|
||||
cmd: string,
|
||||
args: EventArgs,
|
||||
fn?: (output: string) => void,
|
||||
postInputUpdated?: Variable<boolean>,
|
||||
) => void;
|
||||
|
||||
export type ThrottleFnCallback = ((output: string) => void) | undefined;
|
||||
1
src/lib/types/variable.d.ts
vendored
Normal file
1
src/lib/types/variable.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export type Bind = OriginalBinding<GObject.Object, keyof Props<GObject.Object>, unknown>;
|
||||
3
src/lib/types/volume.d.ts
vendored
Normal file
3
src/lib/types/volume.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export type VolumeIcons = {
|
||||
[index: number]: string;
|
||||
};
|
||||
118
src/lib/types/weather.d.ts
vendored
Normal file
118
src/lib/types/weather.d.ts
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
import { weatherIcons } from 'src/lib/icons/weather';
|
||||
|
||||
export type UnitType = 'imperial' | 'metric';
|
||||
|
||||
export type Weather = {
|
||||
location: Location;
|
||||
current: Current;
|
||||
forecast: Forecast;
|
||||
};
|
||||
|
||||
export type Current = {
|
||||
last_updated_epoch?: number;
|
||||
last_updated?: string;
|
||||
temp_c: number;
|
||||
temp_f: number;
|
||||
is_day: number;
|
||||
condition: Condition;
|
||||
wind_mph: number;
|
||||
wind_kph: number;
|
||||
wind_degree: number;
|
||||
wind_dir: string;
|
||||
pressure_mb: number;
|
||||
pressure_in: number;
|
||||
precip_mm: number;
|
||||
precip_in: number;
|
||||
humidity: number;
|
||||
cloud: number;
|
||||
feelslike_c: number;
|
||||
feelslike_f: number;
|
||||
windchill_c: number;
|
||||
windchill_f: number;
|
||||
heatindex_c: number;
|
||||
heatindex_f: number;
|
||||
dewpoint_c: number;
|
||||
dewpoint_f: number;
|
||||
vis_km: number;
|
||||
vis_miles: number;
|
||||
uv: number;
|
||||
gust_mph: number;
|
||||
gust_kph: number;
|
||||
time_epoch?: number;
|
||||
time?: string;
|
||||
snow_cm?: number;
|
||||
will_it_rain?: number;
|
||||
chance_of_rain?: number;
|
||||
will_it_snow?: number;
|
||||
chance_of_snow?: number;
|
||||
};
|
||||
|
||||
export type Condition = {
|
||||
text: string;
|
||||
icon: string;
|
||||
code: number;
|
||||
};
|
||||
|
||||
export type Forecast = {
|
||||
forecastday: Forecastday[];
|
||||
};
|
||||
|
||||
export type Forecastday = {
|
||||
date: string;
|
||||
date_epoch: number;
|
||||
day: Day;
|
||||
astro: Astro;
|
||||
hour: Current[];
|
||||
};
|
||||
|
||||
export type Astro = {
|
||||
sunrise: string;
|
||||
sunset: string;
|
||||
moonrise: string;
|
||||
moonset: string;
|
||||
moon_phase: string;
|
||||
moon_illumination: number;
|
||||
is_moon_up: number;
|
||||
is_sun_up: number;
|
||||
};
|
||||
|
||||
export type Day = {
|
||||
maxtemp_c: number;
|
||||
maxtemp_f: number;
|
||||
mintemp_c: number;
|
||||
mintemp_f: number;
|
||||
avgtemp_c: number;
|
||||
avgtemp_f: number;
|
||||
maxwind_mph: number;
|
||||
maxwind_kph: number;
|
||||
totalprecip_mm: number;
|
||||
totalprecip_in: number;
|
||||
totalsnow_cm: number;
|
||||
avgvis_km: number;
|
||||
avgvis_miles: number;
|
||||
avghumidity: number;
|
||||
daily_will_it_rain: number;
|
||||
daily_chance_of_rain: number;
|
||||
daily_will_it_snow: number;
|
||||
daily_chance_of_snow: number;
|
||||
condition: Condition;
|
||||
uv: number;
|
||||
};
|
||||
|
||||
export type Location = {
|
||||
name: string;
|
||||
region: string;
|
||||
country: string;
|
||||
lat: number;
|
||||
lon: number;
|
||||
tz_id: string;
|
||||
localtime_epoch: number;
|
||||
localtime: string;
|
||||
};
|
||||
|
||||
export type TemperatureIconColorMap = {
|
||||
[key: number]: string;
|
||||
};
|
||||
|
||||
export type WeatherIconTitle = keyof typeof weatherIcons;
|
||||
export type WeatherIcon = (typeof weatherIcons)[WeatherIconTitle];
|
||||
55
src/lib/types/widget.d.ts
vendored
Normal file
55
src/lib/types/widget.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Gdk, Gtk } from 'astal/gtk3';
|
||||
import Box from 'types/widgets/box';
|
||||
|
||||
export type Exclusivity = 'normal' | 'ignore' | 'exclusive';
|
||||
export type Anchor = 'left' | 'right' | 'top' | 'down';
|
||||
export type Transition = 'none' | 'crossfade' | 'slide_right' | 'slide_left' | 'slide_up' | 'slide_down';
|
||||
|
||||
export type Layouts =
|
||||
| 'center'
|
||||
| 'top'
|
||||
| 'top-right'
|
||||
| 'top-center'
|
||||
| 'top-left'
|
||||
| 'bottom-left'
|
||||
| 'bottom-center'
|
||||
| 'bottom-right';
|
||||
|
||||
export type Attribute = unknown;
|
||||
export type Child = Gtk.Widget;
|
||||
export type GtkWidget = Gtk.Widget;
|
||||
export type BoxWidget = Box<GtkWidget, Child>;
|
||||
export type GdkEvent = Gdk.Event;
|
||||
|
||||
export type GButton = Gtk.Button;
|
||||
export type GBox = Gtk.Box;
|
||||
export type GLabel = Gtk.Label;
|
||||
export type GCenterBox = Gtk.Box;
|
||||
|
||||
export type EventHandler<Self> = (self: Self, event: Gdk.Event) => boolean | unknown;
|
||||
export type EventArgs = {
|
||||
clicked: GtkWidget;
|
||||
event: Gdk.EventButton | Gdk.EventScroll;
|
||||
};
|
||||
|
||||
export interface WidgetProps {
|
||||
onPrimaryClick?: (clicked: GtkWidget, event: Gdk.EventButton) => void;
|
||||
onSecondaryClick?: (clicked: GtkWidget, event: Gdk.EventButton) => void;
|
||||
onMiddleClick?: (clicked: GtkWidget, event: Gdk.EventButton) => void;
|
||||
onScrollUp?: (clicked: GtkWidget, event: Gdk.EventScroll) => void;
|
||||
onScrollDown?: (clicked: GtkWidget, event: Gdk.EventScroll) => void;
|
||||
setup?: (self: GtkWidget) => void;
|
||||
}
|
||||
|
||||
export interface GtkWidgetExtended extends Gtk.Widget {
|
||||
props?: WidgetProps;
|
||||
component?: JSX.Element;
|
||||
primaryClick?: (clicked: GtkWidget, event: Astal.ClickEvent) => void;
|
||||
isVisible?: boolean;
|
||||
boxClass?: string;
|
||||
isVis?: {
|
||||
bind: (key: string) => Bind;
|
||||
};
|
||||
}
|
||||
|
||||
export type GtkWidget = GtkWidgetExtended;
|
||||
36
src/lib/types/workspace.d.ts
vendored
Normal file
36
src/lib/types/workspace.d.ts
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
export type WorkspaceRule = {
|
||||
workspaceString: string;
|
||||
monitor: string;
|
||||
};
|
||||
|
||||
export type WorkspaceMap = {
|
||||
[key: string]: number[];
|
||||
};
|
||||
|
||||
export type MonitorMap = {
|
||||
[key: number]: string;
|
||||
};
|
||||
|
||||
export type ApplicationIcons = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type WorkspaceIcons = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type AppIconOptions = {
|
||||
iconMap: ApplicationIcons;
|
||||
defaultIcon: string;
|
||||
emptyIcon: string;
|
||||
};
|
||||
export type ClientAttributes = [className: string, title: string];
|
||||
|
||||
export type WorkspaceIconsColored = {
|
||||
[key: string]: {
|
||||
color: string;
|
||||
icon: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type WorkspaceIconMap = WorkspaceIcons | WorkspaceIconsColored;
|
||||
414
src/lib/utils.ts
Normal file
414
src/lib/utils.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
import { BarModule, NotificationAnchor, PositionAnchor } from './types/options';
|
||||
import { OSDAnchor } from './types/options';
|
||||
import icons, { substitutes } from './icons/icons';
|
||||
import GLib from 'gi://GLib?version=2.0';
|
||||
import GdkPixbuf from 'gi://GdkPixbuf';
|
||||
import { NotificationArgs } from './types/notification';
|
||||
import { namedColors } from './constants/colors';
|
||||
import { distroIcons } from './constants/distro';
|
||||
import { distro } from './variables';
|
||||
import options from '../options';
|
||||
import { Astal, Gdk, Gtk } from 'astal/gtk3';
|
||||
import AstalApps from 'gi://AstalApps?version=0.1';
|
||||
import { exec, execAsync } from 'astal/process';
|
||||
import { Gio } from 'astal';
|
||||
|
||||
/**
|
||||
* Handles errors by throwing a new Error with a message.
|
||||
*
|
||||
* This function takes an error object and throws a new Error with the provided message or a default message.
|
||||
* If the error is an instance of Error, it uses the error's message. Otherwise, it converts the error to a string.
|
||||
*
|
||||
* @param error The error to handle.
|
||||
*
|
||||
* @throws Throws a new error with the provided message or a default message.
|
||||
*/
|
||||
export function errorHandler(error: unknown): never {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
throw new Error(String(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up an icon by name and size.
|
||||
*
|
||||
* This function retrieves an icon from the default icon theme based on the provided name and size.
|
||||
* If the name is not provided, it returns null.
|
||||
*
|
||||
* @param name The name of the icon to look up.
|
||||
* @param size The size of the icon to look up. Defaults to 16.
|
||||
*
|
||||
* @returns The Gtk.IconInfo object if the icon is found, or null if not found.
|
||||
*/
|
||||
export function lookUpIcon(name?: string, size = 16): Gtk.IconInfo | null {
|
||||
if (!name) return null;
|
||||
|
||||
return Gtk.IconTheme.get_default().lookup_icon(name, size, Gtk.IconLookupFlags.USE_BUILTIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all unique layout items from the bar options.
|
||||
*
|
||||
* This function extracts all unique layout items from the bar options defined in the `options` object.
|
||||
* It iterates through the layouts for each monitor and collects items from the left, middle, and right sections.
|
||||
*
|
||||
* @returns An array of unique layout items.
|
||||
*/
|
||||
export function getLayoutItems(): BarModule[] {
|
||||
const { layouts } = options.bar;
|
||||
|
||||
const itemsInLayout: BarModule[] = [];
|
||||
|
||||
Object.keys(layouts.get()).forEach((monitor) => {
|
||||
const leftItems = layouts.get()[monitor].left;
|
||||
const rightItems = layouts.get()[monitor].right;
|
||||
const middleItems = layouts.get()[monitor].middle;
|
||||
|
||||
itemsInLayout.push(...leftItems);
|
||||
itemsInLayout.push(...middleItems);
|
||||
itemsInLayout.push(...rightItems);
|
||||
});
|
||||
|
||||
return [...new Set(itemsInLayout)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the appropriate icon based on the provided name and fallback.
|
||||
*
|
||||
* This function returns a substitute icon if available, the original name if it exists as a file, or a fallback icon.
|
||||
* It also logs a message if no substitute icon is found.
|
||||
*
|
||||
* @param name The name of the icon to look up.
|
||||
* @param fallback The fallback icon to use if the name is not found. Defaults to `icons.missing`.
|
||||
*
|
||||
* @returns The icon name or the fallback icon.
|
||||
*/
|
||||
export function icon(name: string | null, fallback = icons.missing): string {
|
||||
const validateSubstitute = (name: string): name is keyof typeof substitutes => name in substitutes;
|
||||
|
||||
if (!name) return fallback || '';
|
||||
|
||||
if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
|
||||
|
||||
let icon: string = name;
|
||||
|
||||
if (validateSubstitute(name)) {
|
||||
icon = substitutes[name];
|
||||
}
|
||||
|
||||
if (lookUpIcon(icon)) return icon;
|
||||
|
||||
print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a bash command asynchronously.
|
||||
*
|
||||
* This function runs a bash command using `execAsync` and returns the output as a string.
|
||||
* It handles errors by logging them and returning an empty string.
|
||||
*
|
||||
* @param strings The command to execute as a template string or a regular string.
|
||||
* @param values Additional values to interpolate into the command.
|
||||
*
|
||||
* @returns A promise that resolves to the command output as a string.
|
||||
*/
|
||||
export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]): Promise<string> {
|
||||
const cmd =
|
||||
typeof strings === 'string' ? strings : strings.flatMap((str, i) => str + `${values[i] ?? ''}`).join('');
|
||||
|
||||
return execAsync(['bash', '-c', cmd]).catch((err) => {
|
||||
console.error(cmd, err);
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a shell command asynchronously.
|
||||
*
|
||||
* This function runs a shell command using `execAsync` and returns the output as a string.
|
||||
* It handles errors by logging them and returning an empty string.
|
||||
*
|
||||
* @param cmd The command to execute as a string or an array of strings.
|
||||
*
|
||||
* @returns A promise that resolves to the command output as a string.
|
||||
*/
|
||||
export async function sh(cmd: string | string[]): Promise<string> {
|
||||
return execAsync(cmd).catch((err) => {
|
||||
console.error(typeof cmd === 'string' ? cmd : cmd.join(' '), err);
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of JSX elements for each monitor.
|
||||
*
|
||||
* This function creates an array of JSX elements by calling the provided widget function for each monitor.
|
||||
* It uses the number of monitors available in the default Gdk display.
|
||||
*
|
||||
* @param widget A function that takes a monitor index and returns a JSX element.
|
||||
*
|
||||
* @returns An array of JSX elements, one for each monitor.
|
||||
*/
|
||||
export function forMonitors(widget: (monitor: number) => JSX.Element): JSX.Element[] {
|
||||
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
|
||||
return range(n, 0).flatMap(widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of numbers within a specified range.
|
||||
*
|
||||
* This function creates an array of numbers starting from the `start` value up to the specified `length`.
|
||||
*
|
||||
* @param length The length of the array to generate.
|
||||
* @param start The starting value of the range. Defaults to 1.
|
||||
*
|
||||
* @returns An array of numbers within the specified range.
|
||||
*/
|
||||
export function range(length: number, start = 1): number[] {
|
||||
return Array.from({ length }, (_, i) => i + start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all specified dependencies are available.
|
||||
*
|
||||
* This function verifies the presence of the specified binaries using the `which` command.
|
||||
* It logs a warning and sends a notification if any dependencies are missing.
|
||||
*
|
||||
* @param bins The list of binaries to check.
|
||||
*
|
||||
* @returns True if all dependencies are found, false otherwise.
|
||||
*/
|
||||
export function dependencies(...bins: string[]): boolean {
|
||||
const missing = bins.filter((bin) => {
|
||||
try {
|
||||
exec(`which ${bin}`);
|
||||
return false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.warn(Error(`missing dependencies: ${missing.join(', ')}`));
|
||||
Notify({
|
||||
summary: 'Dependencies not found!',
|
||||
body: `The following dependencies are missing: ${missing.join(', ')}`,
|
||||
iconName: icons.ui.warning,
|
||||
timeout: 7000,
|
||||
});
|
||||
}
|
||||
|
||||
return missing.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches an application in a detached process.
|
||||
*
|
||||
* This function runs the specified application executable in the background using a bash command.
|
||||
* It also increments the application's frequency counter.
|
||||
*
|
||||
* @param app The application to launch.
|
||||
*/
|
||||
export function launchApp(app: AstalApps.Application): void {
|
||||
const exe = app.executable
|
||||
.split(/\s+/)
|
||||
.filter((str) => !str.startsWith('%') && !str.startsWith('@'))
|
||||
.join(' ');
|
||||
|
||||
bash(`${exe} &`);
|
||||
app.frequency += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided filepath is a valid image.
|
||||
*
|
||||
* This function attempts to load an image from the specified filepath using GdkPixbuf.
|
||||
* If the image is successfully loaded, it returns true. Otherwise, it logs an error and returns false.
|
||||
*
|
||||
* @param imgFilePath The path to the image file.
|
||||
*
|
||||
* @returns True if the filepath is a valid image, false otherwise.
|
||||
*/
|
||||
export function isAnImage(imgFilePath: string): boolean {
|
||||
try {
|
||||
const file = Gio.File.new_for_path(imgFilePath);
|
||||
if (!file.query_exists(null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GdkPixbuf.Pixbuf.new_from_file(imgFilePath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification using the `notify-send` command.
|
||||
*
|
||||
* This function constructs a notification command based on the provided notification arguments and executes it asynchronously.
|
||||
* It logs an error if the notification fails to send.
|
||||
*
|
||||
* @param notifPayload The notification arguments containing summary, body, appName, iconName, urgency, timeout, category, transient, and id.
|
||||
*/
|
||||
export function Notify(notifPayload: NotificationArgs): void {
|
||||
let command = 'notify-send';
|
||||
command += ` "${notifPayload.summary} "`;
|
||||
if (notifPayload.body) command += ` "${notifPayload.body}" `;
|
||||
if (notifPayload.appName) command += ` -a "${notifPayload.appName}"`;
|
||||
if (notifPayload.iconName) command += ` -i "${notifPayload.iconName}"`;
|
||||
if (notifPayload.urgency) command += ` -u "${notifPayload.urgency}"`;
|
||||
if (notifPayload.timeout !== undefined) command += ` -t ${notifPayload.timeout}`;
|
||||
if (notifPayload.category) command += ` -c "${notifPayload.category}"`;
|
||||
if (notifPayload.transient) command += ` -e`;
|
||||
if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`;
|
||||
|
||||
execAsync(command)
|
||||
.then()
|
||||
.catch((err) => {
|
||||
console.error(`Failed to send notification: ${err.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a notification or OSD anchor position to an Astal window anchor.
|
||||
*
|
||||
* This function converts a position anchor from the notification or OSD settings to the corresponding Astal window anchor.
|
||||
*
|
||||
* @param pos The position anchor to convert.
|
||||
*
|
||||
* @returns The corresponding Astal window anchor.
|
||||
*/
|
||||
export function getPosition(pos: NotificationAnchor | OSDAnchor): Astal.WindowAnchor {
|
||||
const positionMap: PositionAnchor = {
|
||||
top: Astal.WindowAnchor.TOP,
|
||||
'top right': Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT,
|
||||
'top left': Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
|
||||
bottom: Astal.WindowAnchor.BOTTOM,
|
||||
'bottom right': Astal.WindowAnchor.BOTTOM | Astal.WindowAnchor.RIGHT,
|
||||
'bottom left': Astal.WindowAnchor.BOTTOM | Astal.WindowAnchor.LEFT,
|
||||
right: Astal.WindowAnchor.RIGHT,
|
||||
left: Astal.WindowAnchor.LEFT,
|
||||
};
|
||||
|
||||
return positionMap[pos] || Astal.WindowAnchor.TOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a string is a valid GJS color.
|
||||
*
|
||||
* This function checks if the provided string is a valid color in GJS.
|
||||
* It supports named colors, hex colors, RGB, and RGBA formats.
|
||||
*
|
||||
* @param color The color string to validate.
|
||||
*
|
||||
* @returns True if the color is valid, false otherwise.
|
||||
*/
|
||||
export function isValidGjsColor(color: string): boolean {
|
||||
const colorLower = color.toLowerCase().trim();
|
||||
|
||||
if (namedColors.has(colorLower)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hexColorRegex = /^#(?:[a-fA-F0-9]{3,4}|[a-fA-F0-9]{6,8})$/;
|
||||
|
||||
const rgbRegex = /^rgb\(\s*(\d{1,3}%?\s*,\s*){2}\d{1,3}%?\s*\)$/;
|
||||
const rgbaRegex = /^rgba\(\s*(\d{1,3}%?\s*,\s*){3}(0|1|0?\.\d+)\s*\)$/;
|
||||
|
||||
if (hexColorRegex.test(color)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rgbRegex.test(colorLower) || rgbaRegex.test(colorLower)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalizes the first letter of a string.
|
||||
*
|
||||
* This function takes a string and returns a new string with the first letter capitalized.
|
||||
*
|
||||
* @param str The string to capitalize.
|
||||
*
|
||||
* @returns The input string with the first letter capitalized.
|
||||
*/
|
||||
export function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the icon for the current distribution.
|
||||
*
|
||||
* This function returns the icon associated with the current distribution based on the `distroIcons` array.
|
||||
* If no icon is found, it returns a default icon.
|
||||
*
|
||||
* @returns The icon for the current distribution as a string.
|
||||
*/
|
||||
export function getDistroIcon(): string {
|
||||
const icon = distroIcons.find(([id]) => id === distro.id);
|
||||
return icon ? icon[1] : ''; // default icon if not found
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an event is a primary click.
|
||||
*
|
||||
* This function determines if the provided event is a primary click based on the button property.
|
||||
*
|
||||
* @param event The click event to check.
|
||||
*
|
||||
* @returns True if the event is a primary click, false otherwise.
|
||||
*/
|
||||
export const isPrimaryClick = (event: Astal.ClickEvent): boolean => event.button === Gdk.BUTTON_PRIMARY;
|
||||
|
||||
/**
|
||||
* Checks if an event is a secondary click.
|
||||
*
|
||||
* This function determines if the provided event is a secondary click based on the button property.
|
||||
*
|
||||
* @param event The click event to check.
|
||||
*
|
||||
* @returns True if the event is a secondary click, false otherwise.
|
||||
*/
|
||||
export const isSecondaryClick = (event: Astal.ClickEvent): boolean => event.button === Gdk.BUTTON_SECONDARY;
|
||||
|
||||
/**
|
||||
* Checks if an event is a middle click.
|
||||
*
|
||||
* This function determines if the provided event is a middle click based on the button property.
|
||||
*
|
||||
* @param event The click event to check.
|
||||
*
|
||||
* @returns True if the event is a middle click, false otherwise.
|
||||
*/
|
||||
export const isMiddleClick = (event: Astal.ClickEvent): boolean => event.button === Gdk.BUTTON_MIDDLE;
|
||||
|
||||
/**
|
||||
* Checks if an event is a scroll up.
|
||||
*
|
||||
* This function determines if the provided event is a scroll up based on the direction property.
|
||||
*
|
||||
* @param event The scroll event to check.
|
||||
*
|
||||
* @returns True if the event is a scroll up, false otherwise.
|
||||
*/
|
||||
export const isScrollUp = (event: Astal.ScrollEvent): boolean => event.direction === Gdk.ScrollDirection.UP;
|
||||
|
||||
/**
|
||||
* Checks if an event is a scroll down.
|
||||
*
|
||||
* This function determines if the provided event is a scroll down based on the direction property.
|
||||
*
|
||||
* @param event The scroll event to check.
|
||||
*
|
||||
* @returns True if the event is a scroll down, false otherwise.
|
||||
*/
|
||||
export const isScrollDown = (event: Astal.ScrollEvent): boolean => event.direction === Gdk.ScrollDirection.DOWN;
|
||||
18
src/lib/variables.ts
Normal file
18
src/lib/variables.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Variable } from 'astal';
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
export const clock = Variable(GLib.DateTime.new_now_local()).poll(
|
||||
1000,
|
||||
(): GLib.DateTime => GLib.DateTime.new_now_local(),
|
||||
);
|
||||
|
||||
export const uptime = Variable(0).poll(
|
||||
60_00,
|
||||
'cat /proc/uptime',
|
||||
(line): number => Number.parseInt(line.split('.')[0]) / 60,
|
||||
);
|
||||
|
||||
export const distro = {
|
||||
id: GLib.get_os_info('ID'),
|
||||
logo: GLib.get_os_info('LOGO'),
|
||||
};
|
||||
Reference in New Issue
Block a user