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;
};