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:
361
src/components/bar/modules/workspaces/helpers/index.ts
Normal file
361
src/components/bar/modules/workspaces/helpers/index.ts
Normal 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;
|
||||
};
|
||||
274
src/components/bar/modules/workspaces/helpers/utils.ts
Normal file
274
src/components/bar/modules/workspaces/helpers/utils.ts
Normal 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}`;
|
||||
};
|
||||
53
src/components/bar/modules/workspaces/index.tsx
Normal file
53
src/components/bar/modules/workspaces/index.tsx
Normal 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 };
|
||||
178
src/components/bar/modules/workspaces/workspaces.tsx
Normal file
178
src/components/bar/modules/workspaces/workspaces.tsx
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user