* Clean up unused code * Fix media player formatting issue for labels with new line characteres. * Refactor the media player handlers into a class. * More code cleanup and organize shared weather utils into distinct classes. * Flatten some nesting. * Move weather manager in dedicated class and build HTTP Utility class for Rest API calling. * Remove logs * Rebase master merge * Reorg code (WIP) * More reorg * Delete utility scripts * Reorg options * Finish moving all options over * Fix typescript issues * Update options imports to default * missed update * Screw barrel files honestly, work of the devil. * Only initialize power profiles if power-profiles-daemon is running. * Fix window positioning and weather service naming * style dir * More organization * Restructure types to be closer to their source * Remove lib types and constants * Update basic weather object to be saner with extensibility. * Service updates * Fix initialization strategy for services. * Fix Config Manager to only emit changed objects and added missing temp converters. * Update storage service to handle unit changes. * Added cpu temp sensor auto-discovery * Added missing JSDocs to services * remove unused * Migrate to network service. * Fix network password issue. * Move out password input into helper * Rename password mask constant to be less double-negativey. * Dropdown menu rename * Added a component to edit JSON in the settings dialog (rough/WIP) * Align settings * Add and style JSON Editor. * Adjust padding * perf(shortcuts): ⚡ avoid unnecessary polling when shortcuts are disabled Stops the recording poller when shortcuts are disabled, preventing redundant polling and reducing resource usage. * Fix types and return value if shortcut not enabled. * Move the swww daemon checking process outside of the wallpaper service into a dedicated deamon lifecyle processor. * Add more string formatters and use title case for weather status (as it was). * Fix startup errors. * Rgba fix * Remove zod from dependencies --------- Co-authored-by: KernelDiego <gonzalezdiego.contact@gmail.com>
151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
import AstalTray from 'gi://AstalTray?version=0.1';
|
|
import { bind, Gio, Variable } from 'astal';
|
|
import { Gdk, Gtk } from 'astal/gtk3';
|
|
import { BarBoxChild } from 'src/components/bar/types';
|
|
import options from 'src/configuration';
|
|
import { isPrimaryClick, isSecondaryClick, isMiddleClick } from 'src/lib/events/mouse';
|
|
import { SystemUtilities } from 'src/core/system/SystemUtilities';
|
|
|
|
const systemtray = AstalTray.get_default();
|
|
const { ignore, customIcons } = options.bar.systray;
|
|
|
|
const createMenu = (menuModel: Gio.MenuModel, actionGroup: Gio.ActionGroup | null): Gtk.Menu => {
|
|
const menu = Gtk.Menu.new_from_model(menuModel);
|
|
menu.insert_action_group('dbusmenu', actionGroup);
|
|
|
|
return menu;
|
|
};
|
|
|
|
const MenuCustomIcon = ({ iconLabel, iconColor, item }: MenuCustomIconProps): JSX.Element => {
|
|
return (
|
|
<label
|
|
className={'systray-icon txt-icon'}
|
|
label={iconLabel}
|
|
css={iconColor ? `color: ${iconColor}` : ''}
|
|
tooltipMarkup={bind(item, 'tooltipMarkup')}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const MenuDefaultIcon = ({ item }: MenuEntryProps): JSX.Element => {
|
|
return (
|
|
<icon
|
|
className={'systray-icon'}
|
|
gicon={bind(item, 'gicon')}
|
|
tooltipMarkup={bind(item, 'tooltipMarkup')}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const MenuEntry = ({ item, child }: MenuEntryProps): JSX.Element => {
|
|
let menu: Gtk.Menu;
|
|
|
|
const entryBinding = Variable.derive(
|
|
[bind(item, 'menuModel'), bind(item, 'actionGroup')],
|
|
(menuModel, actionGroup) => {
|
|
if (menuModel === null) {
|
|
return console.error(`Menu Model not found for ${item.id}`);
|
|
}
|
|
if (actionGroup === null) {
|
|
return console.error(`Action Group not found for ${item.id}`);
|
|
}
|
|
|
|
menu = createMenu(menuModel, actionGroup);
|
|
},
|
|
);
|
|
|
|
return (
|
|
<button
|
|
cursor={'pointer'}
|
|
onClick={(self, event) => {
|
|
if (isPrimaryClick(event)) {
|
|
item.activate(0, 0);
|
|
}
|
|
|
|
if (isSecondaryClick(event)) {
|
|
menu?.popup_at_widget(self, Gdk.Gravity.NORTH, Gdk.Gravity.SOUTH, null);
|
|
}
|
|
|
|
if (isMiddleClick(event)) {
|
|
SystemUtilities.notify({ summary: 'App Name', body: item.id });
|
|
}
|
|
}}
|
|
onDestroy={() => {
|
|
menu?.destroy();
|
|
entryBinding.drop();
|
|
}}
|
|
>
|
|
{child}
|
|
</button>
|
|
);
|
|
};
|
|
|
|
const SysTray = (): BarBoxChild => {
|
|
const isVis = Variable(false);
|
|
|
|
const componentChildren = Variable.derive(
|
|
[bind(systemtray, 'items'), bind(ignore), bind(customIcons)],
|
|
(items, ignored, custIcons) => {
|
|
const filteredTray = items.filter(({ id }) => !ignored.includes(id) && id !== null);
|
|
|
|
isVis.set(filteredTray.length > 0);
|
|
|
|
return filteredTray.map((item) => {
|
|
const matchedCustomIcon = Object.keys(custIcons).find((iconRegex) =>
|
|
item.id.match(iconRegex),
|
|
);
|
|
|
|
if (matchedCustomIcon !== undefined) {
|
|
const iconLabel = custIcons[matchedCustomIcon].icon || '';
|
|
const iconColor = custIcons[matchedCustomIcon].color;
|
|
|
|
return (
|
|
<MenuEntry item={item}>
|
|
<MenuCustomIcon iconLabel={iconLabel} iconColor={iconColor} item={item} />
|
|
</MenuEntry>
|
|
);
|
|
}
|
|
return (
|
|
<MenuEntry item={item}>
|
|
<MenuDefaultIcon item={item} />
|
|
</MenuEntry>
|
|
);
|
|
});
|
|
},
|
|
);
|
|
|
|
const component = (
|
|
<box
|
|
className={'systray-container'}
|
|
onDestroy={() => {
|
|
isVis.drop();
|
|
componentChildren.drop();
|
|
}}
|
|
>
|
|
{componentChildren()}
|
|
</box>
|
|
);
|
|
|
|
return {
|
|
component,
|
|
isVisible: true,
|
|
boxClass: 'systray',
|
|
isVis: bind(isVis),
|
|
isBox: true,
|
|
props: {},
|
|
};
|
|
};
|
|
|
|
interface MenuCustomIconProps {
|
|
iconLabel: string;
|
|
iconColor: string;
|
|
item: AstalTray.TrayItem;
|
|
}
|
|
|
|
interface MenuEntryProps {
|
|
item: AstalTray.TrayItem;
|
|
child?: JSX.Element;
|
|
}
|
|
|
|
export { SysTray };
|