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:
Jas Singh
2024-12-20 18:10:10 -08:00
committed by GitHub
parent 955eed6c60
commit 2ffd602910
605 changed files with 19543 additions and 15999 deletions

View File

@@ -0,0 +1,361 @@
import { exec, Variable } from 'astal';
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { hyprlandService } from 'src/lib/constants/services';
import { MonitorMap, WorkspaceMap, WorkspaceRule } from 'src/lib/types/workspace';
import { range } from 'src/lib/utils';
import options from 'src/options';
const { workspaces, reverse_scroll, ignored } = options.bar.workspaces;
/**
* Retrieves the workspaces for a specific monitor.
*
* This function checks if a given workspace is valid for a specified monitor based on the workspace rules.
*
* @param curWs - The current workspace number.
* @param wsRules - The workspace rules map.
* @param monitor - The monitor ID.
* @param workspaceList - The list of workspaces.
* @param monitorList - The list of monitors.
*
* @returns Whether the workspace is valid for the monitor.
*/
export const getWorkspacesForMonitor = (
curWs: number,
wsRules: WorkspaceMap,
monitor: number,
workspaceList: AstalHyprland.Workspace[],
monitorList: AstalHyprland.Monitor[],
): boolean => {
if (!wsRules || !Object.keys(wsRules).length) {
return true;
}
const monitorMap: MonitorMap = {};
const workspaceMonitorList = workspaceList.map((m) => {
return { id: m.monitor.id, name: m.monitor.name };
});
const monitors = [...new Map([...workspaceMonitorList, ...monitorList].map((item) => [item.id, item])).values()];
monitors.forEach((mon) => (monitorMap[mon.id] = mon.name));
const currentMonitorName = monitorMap[monitor];
const monitorWSRules = wsRules[currentMonitorName];
if (monitorWSRules === undefined) {
return true;
}
return monitorWSRules.includes(curWs);
};
/**
* Retrieves the workspace rules.
*
* This function fetches and parses the workspace rules from the Hyprland service.
*
* @returns The workspace rules map.
*/
export const getWorkspaceRules = (): WorkspaceMap => {
try {
const rules = exec('hyprctl workspacerules -j');
const workspaceRules: WorkspaceMap = {};
JSON.parse(rules).forEach((rule: WorkspaceRule) => {
const workspaceNum = parseInt(rule.workspaceString, 10);
if (isNaN(workspaceNum)) {
return;
}
if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) {
workspaceRules[rule.monitor].push(workspaceNum);
} else {
workspaceRules[rule.monitor] = [workspaceNum];
}
});
return workspaceRules;
} catch (err) {
console.error(err);
return {};
}
};
/**
* Retrieves the current monitor's workspaces.
*
* This function returns a list of workspace numbers for the specified monitor.
*
* @param monitor - The monitor ID.
*
* @returns The list of workspace numbers.
*/
export const getCurrentMonitorWorkspaces = (monitor: number): number[] => {
if (hyprlandService.get_monitors().length === 1) {
return Array.from({ length: workspaces.get() }, (_, i) => i + 1);
}
const monitorWorkspaces = getWorkspaceRules();
const monitorMap: MonitorMap = {};
hyprlandService.get_monitors().forEach((m) => (monitorMap[m.id] = m.name));
const currentMonitorName = monitorMap[monitor];
return monitorWorkspaces[currentMonitorName];
};
/**
* Checks if a workspace is ignored.
*
* This function determines if a given workspace number is in the ignored workspaces list.
*
* @param ignoredWorkspaces - The ignored workspaces variable.
* @param workspaceNumber - The workspace number.
*
* @returns Whether the workspace is ignored.
*/
export const isWorkspaceIgnored = (ignoredWorkspaces: Variable<string>, workspaceNumber: number): boolean => {
if (ignoredWorkspaces.get() === '') return false;
const ignoredWsRegex = new RegExp(ignoredWorkspaces.get());
return ignoredWsRegex.test(workspaceNumber.toString());
};
/**
* Navigates to the next or previous workspace.
*
* This function changes the current workspace to the next or previous one, considering active and ignored workspaces.
*
* @param direction - The direction to navigate ('next' or 'prev').
* @param currentMonitorWorkspaces - The current monitor's workspaces variable.
* @param activeWorkspaces - Whether to consider only active workspaces.
* @param ignoredWorkspaces - The ignored workspaces variable.
*/
const navigateWorkspace = (
direction: 'next' | 'prev',
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean,
ignoredWorkspaces: Variable<string>,
): void => {
const hyprlandWorkspaces = hyprlandService.get_workspaces() || [];
const occupiedWorkspaces = hyprlandWorkspaces
.filter((ws) => hyprlandService.focusedMonitor.id === ws.monitor?.id)
.map((ws) => ws.id);
const workspacesList = activeWorkspaces
? occupiedWorkspaces
: currentMonitorWorkspaces.get() || Array.from({ length: workspaces.get() }, (_, i) => i + 1);
if (workspacesList.length === 0) return;
const currentIndex = workspacesList.indexOf(hyprlandService.focusedWorkspace.id);
const step = direction === 'next' ? 1 : -1;
let newIndex = (currentIndex + step + workspacesList.length) % workspacesList.length;
let attempts = 0;
while (attempts < workspacesList.length) {
const targetWS = workspacesList[newIndex];
if (!isWorkspaceIgnored(ignoredWorkspaces, targetWS)) {
hyprlandService.message_async(`dispatch workspace ${targetWS}`);
return;
}
newIndex = (newIndex + step + workspacesList.length) % workspacesList.length;
attempts++;
}
};
/**
* Navigates to the next workspace.
*
* This function changes the current workspace to the next one.
*
* @param currentMonitorWorkspaces - The current monitor's workspaces variable.
* @param activeWorkspaces - Whether to consider only active workspaces.
* @param ignoredWorkspaces - The ignored workspaces variable.
*/
export const goToNextWS = (
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean,
ignoredWorkspaces: Variable<string>,
): void => {
navigateWorkspace('next', currentMonitorWorkspaces, activeWorkspaces, ignoredWorkspaces);
};
/**
* Navigates to the previous workspace.
*
* This function changes the current workspace to the previous one.
*
* @param currentMonitorWorkspaces - The current monitor's workspaces variable.
* @param activeWorkspaces - Whether to consider only active workspaces.
* @param ignoredWorkspaces - The ignored workspaces variable.
*/
export const goToPrevWS = (
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean,
ignoredWorkspaces: Variable<string>,
): void => {
navigateWorkspace('prev', currentMonitorWorkspaces, activeWorkspaces, ignoredWorkspaces);
};
/**
* Throttles a function to limit its execution rate.
*
* This function ensures that the provided function is not called more often than the specified limit.
*
* @param func - The function to throttle.
* @param limit - The time limit in milliseconds.
*
* @returns The throttled function.
*/
export function throttle<T extends (...args: unknown[]) => void>(func: T, limit: number): T {
let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
} as T;
}
/**
* Creates throttled scroll handlers for navigating workspaces.
*
* This function returns handlers for scrolling up and down through workspaces, throttled by the specified scroll speed.
*
* @param scrollSpeed - The scroll speed.
* @param currentMonitorWorkspaces - The current monitor's workspaces variable.
* @param activeWorkspaces - Whether to consider only active workspaces.
*
* @returns The throttled scroll handlers.
*/
export const createThrottledScrollHandlers = (
scrollSpeed: number,
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean = true,
): ThrottledScrollHandlers => {
const throttledScrollUp = throttle(() => {
if (reverse_scroll.get()) {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces, ignored);
} else {
goToNextWS(currentMonitorWorkspaces, activeWorkspaces, ignored);
}
}, 200 / scrollSpeed);
const throttledScrollDown = throttle(() => {
if (reverse_scroll.get()) {
goToNextWS(currentMonitorWorkspaces, activeWorkspaces, ignored);
} else {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces, ignored);
}
}, 200 / scrollSpeed);
return { throttledScrollUp, throttledScrollDown };
};
/**
* Retrieves the workspaces to render.
*
* This function returns a list of workspace numbers to render based on the total workspaces, workspace list, rules, and monitor.
*
* @param totalWorkspaces - The total number of workspaces.
* @param workspaceList - The list of workspaces.
* @param workspaceRules - The workspace rules map.
* @param monitor - The monitor ID.
* @param isMonitorSpecific - Whether the workspaces are monitor-specific.
* @param monitorList - The list of monitors.
*
* @returns The list of workspace numbers to render.
*/
export const getWorkspacesToRender = (
totalWorkspaces: number,
workspaceList: AstalHyprland.Workspace[],
workspaceRules: WorkspaceMap,
monitor: number,
isMonitorSpecific: boolean,
monitorList: AstalHyprland.Monitor[],
): number[] => {
let allWorkspaces = range(totalWorkspaces || 8);
const activeWorkspaces = workspaceList.map((ws) => ws.id);
const workspaceMonitorList = workspaceList.map((ws) => {
return {
id: ws.monitor?.id || -1,
name: ws.monitor?.name || '',
};
});
const curMonitor =
monitorList.find((mon) => mon.id === monitor) || workspaceMonitorList.find((mon) => mon.id === monitor);
const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => {
return [...acc, ...workspaceRules[k]];
}, []);
const activesForMonitor = activeWorkspaces.filter((w) => {
if (
curMonitor &&
Object.hasOwnProperty.call(workspaceRules, curMonitor.name) &&
workspacesWithRules.includes(w)
) {
return workspaceRules[curMonitor.name].includes(w);
}
return true;
});
if (isMonitorSpecific) {
const workspacesInRange = range(totalWorkspaces).filter((ws) => {
return getWorkspacesForMonitor(ws, workspaceRules, monitor, workspaceList, monitorList);
});
allWorkspaces = [...new Set([...activesForMonitor, ...workspacesInRange])];
} else {
allWorkspaces = [...new Set([...allWorkspaces, ...activeWorkspaces])];
}
return allWorkspaces.sort((a, b) => a - b);
};
/**
* The workspace rules variable.
* This variable holds the current workspace rules.
*/
export const workspaceRules = Variable(getWorkspaceRules());
/**
* The force updater variable.
* This variable is used to force updates when workspace events occur.
*/
export const forceUpdater = Variable(true);
/**
* Sets up connections for workspace events.
* This function sets up event listeners for various workspace-related events to update the workspace rules and force updates.
*/
export const setupConnections = (): void => {
hyprlandService.connect('config-reloaded', () => {
workspaceRules.set(getWorkspaceRules());
});
hyprlandService.connect('client-moved', () => {
forceUpdater.set(!forceUpdater.get());
});
hyprlandService.connect('client-added', () => {
forceUpdater.set(!forceUpdater.get());
});
hyprlandService.connect('client-removed', () => {
forceUpdater.set(!forceUpdater.get());
});
};
type ThrottledScrollHandlers = {
throttledScrollUp: () => void;
throttledScrollDown: () => void;
};

View File

@@ -0,0 +1,274 @@
import { hyprlandService } from 'src/lib/constants/services';
import { defaultApplicationIcons } from 'src/lib/constants/workspaces';
import { AppIconOptions, WorkspaceIconMap } from 'src/lib/types/workspace';
import { isValidGjsColor } from 'src/lib/utils';
import options from 'src/options';
const { monochrome, background } = options.theme.bar.buttons;
const { background: wsBackground, active } = options.theme.bar.buttons.workspaces;
const { showWsIcons, showAllActive, numbered_active_indicator: wsActiveIndicator } = options.bar.workspaces;
/**
* Determines if a workspace is active on a given monitor.
*
* This function checks if the workspace with the specified index is currently active on the given monitor.
* It uses the `showAllActive` setting and the `hyprlandService` to determine the active workspace on the monitor.
*
* @param monitor The index of the monitor to check.
* @param i The index of the workspace to check.
*
* @returns True if the workspace is active on the monitor, false otherwise.
*/
const isWorkspaceActiveOnMonitor = (monitor: number, i: number): boolean => {
return showAllActive.get() && hyprlandService.get_monitor(monitor).activeWorkspace.id === i;
};
/**
* Retrieves the icon for a given workspace.
*
* This function returns the icon associated with a workspace from the provided workspace icon map.
* If no icon is found, it returns the workspace index as a string.
*
* @param wsIconMap The map of workspace icons where keys are workspace indices and values are icons or icon objects.
* @param i The index of the workspace for which to retrieve the icon.
*
* @returns The icon for the workspace as a string. If no icon is found, returns the workspace index as a string.
*/
const getWsIcon = (wsIconMap: WorkspaceIconMap, i: number): string => {
const iconEntry = wsIconMap[i];
if (!iconEntry) {
return `${i}`;
}
const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== '';
if (typeof iconEntry === 'string' && iconEntry !== '') {
return iconEntry;
}
if (hasIcon) {
return iconEntry.icon;
}
return `${i}`;
};
/**
* Retrieves the color for a given workspace.
*
* This function determines the color styling for a workspace based on the provided workspace icon map,
* smart highlighting settings, and the monitor index. It returns a CSS string for the color and background.
*
* @param wsIconMap The map of workspace icons where keys are workspace indices and values are icon objects.
* @param i The index of the workspace for which to retrieve the color.
* @param smartHighlight A boolean indicating whether smart highlighting is enabled.
* @param monitor The index of the monitor to check for active workspaces.
*
* @returns A CSS string representing the color and background for the workspace. If no color is found, returns an empty string.
*/
export const getWsColor = (
wsIconMap: WorkspaceIconMap,
i: number,
smartHighlight: boolean,
monitor: number,
): string => {
const iconEntry = wsIconMap[i];
const hasColor = typeof iconEntry === 'object' && 'color' in iconEntry && isValidGjsColor(iconEntry.color);
if (!iconEntry) {
return '';
}
if (
showWsIcons.get() &&
smartHighlight &&
wsActiveIndicator.get() === 'highlight' &&
(hyprlandService.focusedWorkspace.id === i || isWorkspaceActiveOnMonitor(monitor, i))
) {
const iconColor = monochrome.get() ? background.get() : wsBackground.get();
const iconBackground = hasColor && isValidGjsColor(iconEntry.color) ? iconEntry.color : active.get();
const colorCss = `color: ${iconColor};`;
const backgroundCss = `background: ${iconBackground};`;
return colorCss + backgroundCss;
}
if (hasColor && isValidGjsColor(iconEntry.color)) {
return `color: ${iconEntry.color}; border-bottom-color: ${iconEntry.color};`;
}
return '';
};
/**
* Retrieves the application icon for a given workspace.
*
* This function returns the appropriate application icon for the specified workspace index.
* It considers user-defined icons, default icons, and the option to remove duplicate icons.
*
* @param workspaceIndex The index of the workspace for which to retrieve the application icon.
* @param removeDuplicateIcons A boolean indicating whether to remove duplicate icons.
* @param options An object containing user-defined icon map, default icon, and empty icon.
*
* @returns The application icon for the workspace as a string. If no icons are found, returns the default or empty icon.
*/
export const getAppIcon = (
workspaceIndex: number,
removeDuplicateIcons: boolean,
{ iconMap: userDefinedIconMap, defaultIcon, emptyIcon }: AppIconOptions,
): string => {
const iconMap = { ...userDefinedIconMap, ...defaultApplicationIcons };
const clients = hyprlandService
.get_clients()
.filter((client) => client.workspace.id === workspaceIndex)
.map((client) => [client.class, client.title]);
if (!clients.length) {
return emptyIcon;
}
let icons = clients
.map(([clientClass, clientTitle]) => {
const maybeIcon = Object.entries(iconMap).find(([matcher]) => {
try {
if (matcher.startsWith('class:')) {
const re = matcher.substring(6);
return new RegExp(re).test(clientClass);
}
if (matcher.startsWith('title:')) {
const re = matcher.substring(6);
return new RegExp(re).test(clientTitle);
}
return new RegExp(matcher, 'i').test(clientClass);
} catch {
return false;
}
});
if (!maybeIcon) {
return undefined;
}
return maybeIcon.at(1);
})
.filter((x) => x);
if (removeDuplicateIcons) {
icons = [...new Set(icons)];
}
if (icons.length) {
return icons.join(' ');
}
return defaultIcon;
};
/**
* Renders the class names for a workspace.
*
* This function generates the appropriate class names for a workspace based on various settings such as
* whether to show icons, numbered workspaces, workspace icons, and smart highlighting.
*
* @param showIcons A boolean indicating whether to show icons.
* @param showNumbered A boolean indicating whether to show numbered workspaces.
* @param numberedActiveIndicator The indicator for active numbered workspaces.
* @param showWsIcons A boolean indicating whether to show workspace icons.
* @param smartHighlight A boolean indicating whether smart highlighting is enabled.
* @param monitor The index of the monitor to check for active workspaces.
* @param i The index of the workspace for which to render class names.
*
* @returns The class names for the workspace as a string.
*/
export const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
showWsIcons: boolean,
smartHighlight: boolean,
monitor: number,
i: number,
): string => {
if (showIcons) {
return 'workspace-icon txt-icon bar';
}
if (showNumbered || showWsIcons) {
const numActiveInd =
hyprlandService.focusedWorkspace.id === i || isWorkspaceActiveOnMonitor(monitor, i)
? numberedActiveIndicator
: '';
const wsIconClass = showWsIcons ? 'txt-icon' : '';
const smartHighlightClass = smartHighlight ? 'smart-highlight' : '';
const className = `workspace-number can_${numberedActiveIndicator} ${numActiveInd} ${wsIconClass} ${smartHighlightClass}`;
return className.trim();
}
return 'default';
};
/**
* Renders the label for a workspace.
*
* This function generates the appropriate label for a workspace based on various settings such as
* whether to show icons, application icons, workspace icons, and workspace indicators.
*
* @param showIcons A boolean indicating whether to show icons.
* @param availableIndicator The indicator for available workspaces.
* @param activeIndicator The indicator for active workspaces.
* @param occupiedIndicator The indicator for occupied workspaces.
* @param showAppIcons A boolean indicating whether to show application icons.
* @param appIcons The application icons as a string.
* @param workspaceMask A boolean indicating whether to mask the workspace.
* @param showWorkspaceIcons A boolean indicating whether to show workspace icons.
* @param wsIconMap The map of workspace icons where keys are workspace indices and values are icons or icon objects.
* @param i The index of the workspace for which to render the label.
* @param index The index of the workspace in the list.
* @param monitor The index of the monitor to check for active workspaces.
*
* @returns The label for the workspace as a string.
*/
export const renderLabel = (
showIcons: boolean,
availableIndicator: string,
activeIndicator: string,
occupiedIndicator: string,
showAppIcons: boolean,
appIcons: string,
workspaceMask: boolean,
showWorkspaceIcons: boolean,
wsIconMap: WorkspaceIconMap,
i: number,
index: number,
monitor: number,
): string => {
if (showAppIcons) {
return appIcons;
}
if (showIcons) {
if (hyprlandService.focusedWorkspace.id === i || isWorkspaceActiveOnMonitor(monitor, i)) {
return activeIndicator;
}
if ((hyprlandService.get_workspace(i)?.clients.length || 0) > 0) {
return occupiedIndicator;
}
if (monitor !== -1) {
return availableIndicator;
}
}
if (showWorkspaceIcons) {
return getWsIcon(wsIconMap, i);
}
return workspaceMask ? `${index + 1}` : `${i}`;
};

View File

@@ -0,0 +1,53 @@
import options from 'src/options';
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces } from './helpers';
import { BarBoxChild, SelfButton } from 'src/lib/types/bar';
import { WorkspaceModule } from './workspaces';
import { bind, Variable } from 'astal';
import { GtkWidget } from 'src/lib/types/widget';
import { Gdk } from 'astal/gtk3';
const { workspaces, scroll_speed } = options.bar.workspaces;
const Workspaces = (monitor = -1): BarBoxChild => {
const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor));
workspaces.subscribe(() => {
currentMonitorWorkspaces.set(getCurrentMonitorWorkspaces(monitor));
});
const component = (
<box className={'workspaces-box-container'}>
<WorkspaceModule monitor={monitor} />
</box>
);
return {
component,
isVisible: true,
boxClass: 'workspaces',
isBox: true,
props: {
setup: (self: SelfButton): void => {
Variable.derive([bind(scroll_speed)], (scroll_speed) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(
scroll_speed,
currentMonitorWorkspaces,
);
const scrollHandlers = self.connect('scroll-event', (_: GtkWidget, event: Gdk.Event) => {
const eventDirection = event.get_scroll_direction()[1];
if (eventDirection === Gdk.ScrollDirection.UP) {
throttledScrollUp();
} else if (eventDirection === Gdk.ScrollDirection.DOWN) {
throttledScrollDown();
}
});
self.disconnect(scrollHandlers);
});
},
},
};
};
export { Workspaces };

View File

@@ -0,0 +1,178 @@
import { hyprlandService } from 'src/lib/constants/services';
import options from 'src/options';
import { forceUpdater, getWorkspacesToRender, isWorkspaceIgnored, setupConnections, workspaceRules } from './helpers';
import { getAppIcon, getWsColor, renderClassnames, renderLabel } from './helpers/utils';
import { ApplicationIcons, WorkspaceIconMap } from 'src/lib/types/workspace';
import { bind, Variable } from 'astal';
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { Gtk } from 'astal/gtk3';
import { isPrimaryClick } from 'src/lib/utils';
const {
workspaces,
monitorSpecific,
workspaceMask,
spacing,
ignored,
showAllActive,
show_icons,
show_numbered,
numbered_active_indicator,
workspaceIconMap,
showWsIcons,
showApplicationIcons,
applicationIconOncePerWorkspace,
applicationIconMap,
applicationIconEmptyWorkspace,
applicationIconFallback,
} = options.bar.workspaces;
const { available, active, occupied } = options.bar.workspaces.icons;
const { matugen } = options.theme;
const { smartHighlight } = options.theme.bar.buttons.workspaces;
setupConnections();
export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element => {
const boxChildren = Variable.derive(
[
bind(monitorSpecific),
bind(hyprlandService, 'workspaces'),
bind(workspaceMask),
bind(workspaces),
bind(show_icons),
bind(available),
bind(active),
bind(occupied),
bind(show_numbered),
bind(numbered_active_indicator),
bind(spacing),
bind(workspaceIconMap),
bind(showWsIcons),
bind(showApplicationIcons),
bind(applicationIconOncePerWorkspace),
bind(applicationIconMap),
bind(applicationIconEmptyWorkspace),
bind(applicationIconFallback),
bind(matugen),
bind(smartHighlight),
bind(hyprlandService, 'monitors'),
bind(ignored),
bind(showAllActive),
bind(hyprlandService, 'focusedWorkspace'),
bind(workspaceRules),
bind(forceUpdater),
],
(
isMonitorSpecific: boolean,
workspaceList: AstalHyprland.Workspace[],
workspaceMaskFlag: boolean,
totalWorkspaces: number,
displayIcons: boolean,
availableStatus: string,
activeStatus: string,
occupiedStatus: string,
displayNumbered: boolean,
numberedActiveIndicator: string,
spacingValue: number,
workspaceIconMapping: WorkspaceIconMap,
displayWorkspaceIcons: boolean,
displayApplicationIcons: boolean,
appIconOncePerWorkspace: boolean,
applicationIconMapping: ApplicationIcons,
applicationIconEmptyWorkspace: string,
applicationIconFallback: string,
matugenEnabled: boolean,
smartHighlightEnabled: boolean,
monitorList: AstalHyprland.Monitor[],
) => {
const activeWorkspace = hyprlandService.focusedWorkspace.id;
const workspacesToRender = getWorkspacesToRender(
totalWorkspaces,
workspaceList,
workspaceRules.get(),
monitor,
isMonitorSpecific,
monitorList,
);
return workspacesToRender.map((wsId, index) => {
if (isWorkspaceIgnored(ignored, wsId)) {
return <box />;
}
const appIcons = displayApplicationIcons
? getAppIcon(wsId, appIconOncePerWorkspace, {
iconMap: applicationIconMapping,
defaultIcon: applicationIconFallback,
emptyIcon: applicationIconEmptyWorkspace,
})
: '';
return (
<button
className={'workspace-button'}
onClick={(_, event) => {
if (isPrimaryClick(event)) {
hyprlandService.dispatch('workspace', wsId.toString());
}
}}
>
<label
valign={Gtk.Align.CENTER}
css={
`margin: 0rem ${0.375 * spacingValue}rem;` +
`${displayWorkspaceIcons && !matugenEnabled ? getWsColor(workspaceIconMapping, wsId, smartHighlightEnabled, monitor) : ''}`
}
className={renderClassnames(
displayIcons,
displayNumbered,
numberedActiveIndicator,
displayWorkspaceIcons,
smartHighlightEnabled,
monitor,
wsId,
)}
label={renderLabel(
displayIcons,
availableStatus,
activeStatus,
occupiedStatus,
displayApplicationIcons,
appIcons,
workspaceMaskFlag,
displayWorkspaceIcons,
workspaceIconMapping,
wsId,
index,
monitor,
)}
setup={(self) => {
self.toggleClassName('active', activeWorkspace === wsId);
self.toggleClassName(
'occupied',
(hyprlandService.get_workspace(wsId)?.get_clients().length || 0) > 0,
);
}}
/>
</button>
);
});
},
);
return (
<box
onDestroy={() => {
boxChildren.drop();
}}
>
{boxChildren()}
</box>
);
};
interface WorkspaceModuleProps {
monitor: number;
}