This commit is contained in:
benvonh
2025-01-02 07:33:03 +10:00
50 changed files with 592 additions and 489 deletions

1
astal
View File

@@ -1 +0,0 @@
/usr/share/astal/gjs

View File

@@ -315,7 +315,6 @@ in
bar.workspaces.applicationIconEmptyWorkspace = mkStrOption ""; bar.workspaces.applicationIconEmptyWorkspace = mkStrOption "";
bar.workspaces.applicationIconFallback = mkStrOption "󰣆"; bar.workspaces.applicationIconFallback = mkStrOption "󰣆";
bar.workspaces.applicationIconOncePerWorkspace = mkBoolOption true; bar.workspaces.applicationIconOncePerWorkspace = mkBoolOption true;
bar.workspaces.hideUnoccupied = mkBoolOption true;
bar.workspaces.icons.active = mkStrOption ""; bar.workspaces.icons.active = mkStrOption "";
bar.workspaces.icons.available = mkStrOption ""; bar.workspaces.icons.available = mkStrOption "";
bar.workspaces.icons.occupied = mkStrOption ""; bar.workspaces.icons.occupied = mkStrOption "";

View File

@@ -28,7 +28,7 @@ import {
import { WidgetContainer } from './shared/WidgetContainer'; import { WidgetContainer } from './shared/WidgetContainer';
import options from 'src/options'; import options from 'src/options';
import { App, Gtk } from 'astal/gtk3/index'; import { App, Gtk } from 'astal/gtk3';
import Astal from 'gi://Astal?version=3.0'; import Astal from 'gi://Astal?version=3.0';
import { bind, Variable } from 'astal'; import { bind, Variable } from 'astal';

View File

@@ -5,7 +5,7 @@ import options from 'src/options';
import { BarBoxChild } from 'src/lib/types/bar.js'; import { BarBoxChild } from 'src/lib/types/bar.js';
import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js';
import Variable from 'astal/variable'; import Variable from 'astal/variable';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import AstalBattery from 'gi://AstalBattery?version=0.1'; import AstalBattery from 'gi://AstalBattery?version=0.1';
import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers';
import { getBatteryIcon } from './helpers'; import { getBatteryIcon } from './helpers';

View File

@@ -3,8 +3,7 @@ import options from 'src/options.js';
import { openMenu } from '../../utils/menu.js'; import { openMenu } from '../../utils/menu.js';
import { BarBoxChild } from 'src/lib/types/bar.js'; import { BarBoxChild } from 'src/lib/types/bar.js';
import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js';
import { bind } from 'astal/binding.js'; import { Variable, bind } from 'astal';
import Variable from 'astal/variable.js';
import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js';
import AstalBluetooth from 'gi://AstalBluetooth?version=0.1'; import AstalBluetooth from 'gi://AstalBluetooth?version=0.1';
import { Astal } from 'astal/gtk3'; import { Astal } from 'astal/gtk3';

View File

@@ -13,13 +13,13 @@ import { layoutMap } from './layouts';
* This function parses the provided JSON string to extract the keyboard layout information. * This function parses the provided JSON string to extract the keyboard layout information.
* It returns the layout in the specified format, either as a code or a human-readable string. * It returns the layout in the specified format, either as a code or a human-readable string.
* *
* @param obj The JSON string containing the keyboard layout information. * @param layoutData The JSON string containing the keyboard layout information.
* @param format The format in which to return the layout, either 'code' or 'label'. * @param format The format in which to return the layout, either 'code' or 'label'.
* *
* @returns The keyboard layout in the specified format. If no keyboards are found, returns 'Unknown' or 'Unknown Layout'. * @returns The keyboard layout in the specified format. If no keyboards are found, returns 'Unknown' or 'Unknown Layout'.
*/ */
export const getKeyboardLayout = (obj: string, format: KbLabelType): LayoutKeys | LayoutValues => { export const getKeyboardLayout = (layoutData: string, format: KbLabelType): LayoutKeys | LayoutValues => {
const hyprctlDevices: HyprctlDeviceLayout = JSON.parse(obj); const hyprctlDevices: HyprctlDeviceLayout = JSON.parse(layoutData);
const keyboards = hyprctlDevices['keyboards']; const keyboards = hyprctlDevices['keyboards'];
if (keyboards.length === 0) { if (keyboards.length === 0) {

View File

@@ -4,13 +4,22 @@ import { Module } from '../../shared/Module';
import { inputHandler } from 'src/components/bar/utils/helpers'; import { inputHandler } from 'src/components/bar/utils/helpers';
import { getKeyboardLayout } from './helpers'; import { getKeyboardLayout } from './helpers';
import { BarBoxChild } from 'src/lib/types/bar'; import { BarBoxChild } from 'src/lib/types/bar';
import { bind, execAsync } from 'astal'; import { bind } from 'astal';
import { useHook } from 'src/lib/shared/hookHandler'; import { useHook } from 'src/lib/shared/hookHandler';
import { Astal } from 'astal/gtk3'; import { Astal } from 'astal/gtk3';
const { label, labelType, icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } = const { label, labelType, icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } =
options.bar.customModules.kbLayout; options.bar.customModules.kbLayout;
function setLabel(self: Astal.Label): void {
try {
const devices = hyprlandService.message('j/devices');
self.label = getKeyboardLayout(devices, labelType.get());
} catch (error) {
console.error(error);
}
}
export const KbInput = (): BarBoxChild => { export const KbInput = (): BarBoxChild => {
const keyboardModule = Module({ const keyboardModule = Module({
textIcon: bind(icon), textIcon: bind(icon),
@@ -20,25 +29,13 @@ export const KbInput = (): BarBoxChild => {
self, self,
hyprlandService, hyprlandService,
() => { () => {
execAsync('hyprctl devices -j') setLabel(self);
.then((obj) => {
self.label = getKeyboardLayout(obj, labelType.get());
})
.catch((err) => {
console.error(err);
});
}, },
'keyboard-layout', 'keyboard-layout',
); );
useHook(self, labelType, () => { useHook(self, labelType, () => {
execAsync('hyprctl devices -j') setLabel(self);
.then((obj) => {
self.label = getKeyboardLayout(obj, labelType.get());
})
.catch((err) => {
console.error(err);
});
}); });
}, },
boxClass: 'kblayout', boxClass: 'kblayout',

View File

@@ -3,9 +3,8 @@ import options from 'src/options.js';
import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js';
import { generateMediaLabel } from './helpers/index.js'; import { generateMediaLabel } from './helpers/index.js';
import { mprisService } from 'src/lib/constants/services.js'; import { mprisService } from 'src/lib/constants/services.js';
import Variable from 'astal/variable.js';
import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js';
import { bind } from 'astal/binding.js'; import { bind, Variable } from 'astal';
import { BarBoxChild } from 'src/lib/types/bar.js'; import { BarBoxChild } from 'src/lib/types/bar.js';
import { Astal } from 'astal/gtk3'; import { Astal } from 'astal/gtk3';
import { activePlayer, mediaAlbum, mediaArtist, mediaTitle } from 'src/globals/media.js'; import { activePlayer, mediaAlbum, mediaArtist, mediaTitle } from 'src/globals/media.js';

View File

@@ -2,8 +2,7 @@ import { runAsyncCommand, throttledScrollHandler } from '../../utils/helpers.js'
import options from '../../../../options.js'; import options from '../../../../options.js';
import { openMenu } from '../../utils/menu.js'; import { openMenu } from '../../utils/menu.js';
import { getDistroIcon } from '../../../../lib/utils.js'; import { getDistroIcon } from '../../../../lib/utils.js';
import { bind } from 'astal/binding.js'; import { Variable, bind } from 'astal';
import Variable from 'astal/variable.js';
import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js';
import { BarBoxChild } from 'src/lib/types/bar.js'; import { BarBoxChild } from 'src/lib/types/bar.js';
import { Astal } from 'astal/gtk3'; import { Astal } from 'astal/gtk3';

View File

@@ -4,7 +4,6 @@ import AstalTray from 'gi://AstalTray?version=0.1';
import { bind, Gio, Variable } from 'astal'; import { bind, Gio, Variable } from 'astal';
import { BarBoxChild } from 'src/lib/types/bar'; import { BarBoxChild } from 'src/lib/types/bar';
import { Gdk, Gtk } from 'astal/gtk3'; import { Gdk, Gtk } from 'astal/gtk3';
import { BindableChild } from 'astal/gtk3/astalify';
const systemtray = AstalTray.get_default(); const systemtray = AstalTray.get_default();
const { ignore, customIcons } = options.bar.systray; const { ignore, customIcons } = options.bar.systray;
@@ -137,7 +136,7 @@ interface MenuCustomIconProps {
interface MenuEntryProps { interface MenuEntryProps {
item: AstalTray.TrayItem; item: AstalTray.TrayItem;
child?: BindableChild; child?: JSX.Element;
} }
export { SysTray }; export { SysTray };

View File

@@ -2,8 +2,7 @@ import { audioService } from 'src/lib/constants/services.js';
import { openMenu } from '../../utils/menu.js'; import { openMenu } from '../../utils/menu.js';
import options from 'src/options'; import options from 'src/options';
import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js'; import { runAsyncCommand, throttledScrollHandler } from 'src/components/bar/utils/helpers.js';
import Variable from 'astal/variable.js'; import { bind, Variable } from 'astal';
import { bind } from 'astal/binding.js';
import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js'; import { onMiddleClick, onPrimaryClick, onScroll, onSecondaryClick } from 'src/lib/shared/eventHandlers.js';
import { getIcon } from './helpers/index.js'; import { getIcon } from './helpers/index.js';
import { BarBoxChild } from 'src/lib/types/bar.js'; import { BarBoxChild } from 'src/lib/types/bar.js';

View File

@@ -1,350 +1,364 @@
import { exec, Variable } from 'astal'; import { Variable } from 'astal';
import AstalHyprland from 'gi://AstalHyprland?version=0.1'; import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { hyprlandService } from 'src/lib/constants/services'; import { hyprlandService } from 'src/lib/constants/services';
import { MonitorMap, WorkspaceMap, WorkspaceRule } from 'src/lib/types/workspace'; import { MonitorMap, WorkspaceMonitorMap, WorkspaceRule } from 'src/lib/types/workspace';
import { range } from 'src/lib/utils'; import { range } from 'src/lib/utils';
import options from 'src/options'; import options from 'src/options';
const { workspaces, reverse_scroll, ignored } = options.bar.workspaces; const { workspaces, reverse_scroll, ignored } = options.bar.workspaces;
/** /**
* Retrieves the workspaces for a specific monitor. * A Variable that holds the current map of monitors to the workspace numbers assigned to them.
*
* 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 = ( export const workspaceRules = Variable(getWorkspaceMonitorMap());
curWs: number,
wsRules: WorkspaceMap, /**
monitor: number, * A Variable used to force UI or other updates when relevant workspace events occur.
*/
export const forceUpdater = Variable(true);
/**
* Retrieves the workspace numbers associated with a specific monitor.
*
* If only one monitor exists, this will simply return a list of all possible workspaces.
* Otherwise, it will consult the workspace rules to determine which workspace numbers
* belong to the specified monitor.
*
* @param monitorId - The numeric identifier of the monitor.
*
* @returns An array of workspace numbers belonging to the specified monitor.
*/
export function getWorkspacesForMonitor(monitorId: number): number[] {
const allMonitors = hyprlandService.get_monitors();
if (allMonitors.length === 1) {
return Array.from({ length: workspaces.get() }, (_, index) => index + 1);
}
const workspaceMonitorRules = getWorkspaceMonitorMap();
const monitorNameMap: MonitorMap = {};
allMonitors.forEach((monitorInstance) => {
monitorNameMap[monitorInstance.id] = monitorInstance.name;
});
const currentMonitorName = monitorNameMap[monitorId];
return workspaceMonitorRules[currentMonitorName];
}
/**
* Checks whether a given workspace is valid (assigned) for the specified monitor.
*
* This function inspects the workspace rules object to determine if the current workspace belongs
* to the target monitor. If no workspace rules exist, the function defaults to returning `true`.
*
* @param workspaceId - The number representing the current workspace.
* @param workspaceMonitorRules - The map of monitor names to assigned workspace numbers.
* @param monitorId - The numeric identifier for the monitor.
* @param workspaceList - A list of Hyprland workspace objects.
* @param monitorList - A list of Hyprland monitor objects.
*
* @returns `true` if the workspace is assigned to the monitor or if no rules exist. Otherwise, `false`.
*/
function isWorkspaceValidForMonitor(
workspaceId: number,
workspaceMonitorRules: WorkspaceMonitorMap,
monitorId: number,
workspaceList: AstalHyprland.Workspace[], workspaceList: AstalHyprland.Workspace[],
monitorList: AstalHyprland.Monitor[], monitorList: AstalHyprland.Monitor[],
): boolean => { ): boolean {
if (!wsRules || !Object.keys(wsRules).length) { const monitorNameMap: MonitorMap = {};
return true; const allWorkspaceInstances = workspaceList ?? [];
}
const monitorMap: MonitorMap = {}; const workspaceMonitorReferences = allWorkspaceInstances
.filter((workspaceInstance) => workspaceInstance !== null)
const wsList = workspaceList ?? []; .map((workspaceInstance) => {
return {
const workspaceMonitorList = wsList id: workspaceInstance.monitor?.id,
.filter((m) => m !== null) name: workspaceInstance.monitor?.name,
.map((m) => { };
return { id: m.monitor?.id, name: m.monitor?.name };
}); });
const monitors = [...new Map([...workspaceMonitorList, ...monitorList].map((item) => [item.id, item])).values()]; const mergedMonitorInstances = [
...new Map(
[...workspaceMonitorReferences, ...monitorList].map((monitorCandidate) => [
monitorCandidate.id,
monitorCandidate,
]),
).values(),
];
monitors.forEach((mon) => (monitorMap[mon.id] = mon.name)); mergedMonitorInstances.forEach((monitorInstance) => {
monitorNameMap[monitorInstance.id] = monitorInstance.name;
});
const currentMonitorName = monitorMap[monitor]; const currentMonitorName = monitorNameMap[monitorId];
const monitorWSRules = wsRules[currentMonitorName]; const currentMonitorWorkspaceRules = workspaceMonitorRules[currentMonitorName];
if (monitorWSRules === undefined) { if (currentMonitorWorkspaceRules === undefined) {
return true; return false;
} }
return monitorWSRules.includes(curWs);
}; return currentMonitorWorkspaceRules.includes(workspaceId);
}
/** /**
* Retrieves the workspace rules. * Fetches a map of monitors to the workspace numbers that belong to them.
* *
* This function fetches and parses the workspace rules from the Hyprland service. * This function communicates with the Hyprland service to retrieve workspace rules in JSON format.
* Those rules are parsed, and a map of monitor names to lists of assigned workspace numbers is constructed.
* *
* @returns The workspace rules map. * @returns An object where each key is a monitor name, and each value is an array of workspace numbers.
*/ */
export const getWorkspaceRules = (): WorkspaceMap => { function getWorkspaceMonitorMap(): WorkspaceMonitorMap {
try { try {
const rules = exec('hyprctl workspacerules -j'); const rulesResponse = hyprlandService.message('j/workspacerules');
const workspaceMonitorRules: WorkspaceMonitorMap = {};
const parsedWorkspaceRules = JSON.parse(rulesResponse);
const workspaceRules: WorkspaceMap = {}; parsedWorkspaceRules.forEach((rule: WorkspaceRule) => {
const workspaceNumber = parseInt(rule.workspaceString, 10);
JSON.parse(rules).forEach((rule: WorkspaceRule) => { if (rule.monitor === undefined || isNaN(workspaceNumber)) {
const workspaceNum = parseInt(rule.workspaceString, 10);
if (isNaN(workspaceNum)) {
return; return;
} }
if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) {
workspaceRules[rule.monitor].push(workspaceNum); const doesMonitorExistInRules = Object.hasOwnProperty.call(workspaceMonitorRules, rule.monitor);
if (doesMonitorExistInRules) {
workspaceMonitorRules[rule.monitor].push(workspaceNumber);
} else { } else {
workspaceRules[rule.monitor] = [workspaceNum]; workspaceMonitorRules[rule.monitor] = [workspaceNumber];
} }
}); });
return workspaceRules; return workspaceMonitorRules;
} catch (err) { } catch (error) {
console.error(err); console.error(error);
return {}; return {};
} }
}; }
/** /**
* Retrieves the current monitor's workspaces. * Checks if a workspace number should be ignored based on a regular expression.
* *
* This function returns a list of workspace numbers for the specified monitor. * @param ignoredWorkspacesVariable - A Variable object containing a string pattern of ignored workspaces.
* @param workspaceNumber - The numeric representation of the workspace to check.
* *
* @param monitor - The monitor ID. * @returns `true` if the workspace should be ignored, otherwise `false`.
*
* @returns The list of workspace numbers.
*/ */
export const getCurrentMonitorWorkspaces = (monitor: number): number[] => { function isWorkspaceIgnored(ignoredWorkspacesVariable: Variable<string>, workspaceNumber: number): boolean {
if (hyprlandService.get_monitors().length === 1) { if (ignoredWorkspacesVariable.get() === '') {
return Array.from({ length: workspaces.get() }, (_, i) => i + 1); return false;
} }
const monitorWorkspaces = getWorkspaceRules(); const ignoredWorkspacesRegex = new RegExp(ignoredWorkspacesVariable.get());
const monitorMap: MonitorMap = {}; return ignoredWorkspacesRegex.test(workspaceNumber.toString());
hyprlandService.get_monitors().forEach((m) => (monitorMap[m.id] = m.name)); }
const currentMonitorName = monitorMap[monitor];
return monitorWorkspaces[currentMonitorName];
};
/** /**
* Checks if a workspace is ignored. * Changes the active workspace in the specified direction ('next' or 'prev').
* *
* This function determines if a given workspace number is in the ignored workspaces list. * This function uses the current monitor's set of active or assigned workspaces and
* * cycles through them in the chosen direction. It also respects the list of ignored
* @param ignoredWorkspaces - The ignored workspaces variable. * workspaces, skipping any that match the ignored pattern.
* @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 direction - The direction to navigate ('next' or 'prev').
* @param currentMonitorWorkspaces - The current monitor's workspaces variable. * @param currentMonitorWorkspacesVariable - A Variable containing an array of workspace numbers for the current monitor.
* @param activeWorkspaces - Whether to consider only active workspaces. * @param onlyActiveWorkspaces - Whether to only include active (occupied) workspaces when navigating.
* @param ignoredWorkspaces - The ignored workspaces variable. * @param ignoredWorkspacesVariable - A Variable that contains the ignored workspaces pattern.
*/ */
const navigateWorkspace = ( function navigateWorkspace(direction: 'next' | 'prev', ignoredWorkspacesVariable: Variable<string>): void {
direction: 'next' | 'prev', const allHyprlandWorkspaces = hyprlandService.get_workspaces() || [];
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 const activeWorkspaceIds = allHyprlandWorkspaces
? occupiedWorkspaces .filter((workspaceInstance) => hyprlandService.focusedMonitor.id === workspaceInstance.monitor?.id)
: currentMonitorWorkspaces.get() || Array.from({ length: workspaces.get() }, (_, i) => i + 1); .map((workspaceInstance) => workspaceInstance.id);
if (workspacesList.length === 0) return; const assignedOrOccupiedWorkspaces = activeWorkspaceIds.sort((a, b) => a - b);
const currentIndex = workspacesList.indexOf(hyprlandService.focusedWorkspace?.id); if (assignedOrOccupiedWorkspaces.length === 0) {
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.dispatch('workspace', targetWS.toString());
return; return;
} }
newIndex = (newIndex + step + workspacesList.length) % workspacesList.length;
const workspaceIndex = assignedOrOccupiedWorkspaces.indexOf(hyprlandService.focusedWorkspace?.id);
const step = direction === 'next' ? 1 : -1;
let newIndex = (workspaceIndex + step + assignedOrOccupiedWorkspaces.length) % assignedOrOccupiedWorkspaces.length;
let attempts = 0;
while (attempts < assignedOrOccupiedWorkspaces.length) {
const targetWorkspaceNumber = assignedOrOccupiedWorkspaces[newIndex];
if (!isWorkspaceIgnored(ignoredWorkspacesVariable, targetWorkspaceNumber)) {
hyprlandService.dispatch('workspace', targetWorkspaceNumber.toString());
return;
}
newIndex = (newIndex + step + assignedOrOccupiedWorkspaces.length) % assignedOrOccupiedWorkspaces.length;
attempts++; attempts++;
} }
}; }
/** /**
* Navigates to the next workspace. * Navigates to the next workspace in the current monitor.
* *
* This function changes the current workspace to the next one. * @param currentMonitorWorkspacesVariable - A Variable containing workspace numbers for the current monitor.
* * @param onlyActiveWorkspaces - Whether to only navigate among active (occupied) workspaces.
* @param currentMonitorWorkspaces - The current monitor's workspaces variable. * @param ignoredWorkspacesVariable - A Variable that contains the ignored workspaces pattern.
* @param activeWorkspaces - Whether to consider only active workspaces.
* @param ignoredWorkspaces - The ignored workspaces variable.
*/ */
export const goToNextWS = ( export function goToNextWorkspace(ignoredWorkspacesVariable: Variable<string>): void {
currentMonitorWorkspaces: Variable<number[]>, navigateWorkspace('next', ignoredWorkspacesVariable);
activeWorkspaces: boolean, }
ignoredWorkspaces: Variable<string>,
): void => {
navigateWorkspace('next', currentMonitorWorkspaces, activeWorkspaces, ignoredWorkspaces);
};
/** /**
* Navigates to the previous workspace. * Navigates to the previous workspace in the current monitor.
* *
* This function changes the current workspace to the previous one. * @param currentMonitorWorkspacesVariable - A Variable containing workspace numbers for the current monitor.
* * @param onlyActiveWorkspaces - Whether to only navigate among active (occupied) workspaces.
* @param currentMonitorWorkspaces - The current monitor's workspaces variable. * @param ignoredWorkspacesVariable - A Variable that contains the ignored workspaces pattern.
* @param activeWorkspaces - Whether to consider only active workspaces.
* @param ignoredWorkspaces - The ignored workspaces variable.
*/ */
export const goToPrevWS = ( export function goToPreviousWorkspace(ignoredWorkspacesVariable: Variable<string>): void {
currentMonitorWorkspaces: Variable<number[]>, navigateWorkspace('prev', ignoredWorkspacesVariable);
activeWorkspaces: boolean, }
ignoredWorkspaces: Variable<string>,
): void => {
navigateWorkspace('prev', currentMonitorWorkspaces, activeWorkspaces, ignoredWorkspaces);
};
/** /**
* Throttles a function to limit its execution rate. * Limits the execution rate of a given function to prevent it from being called too often.
* *
* This function ensures that the provided function is not called more often than the specified limit. * @param func - The function to be throttled.
* @param limit - The time limit (in milliseconds) during which calls to `func` are disallowed after the first call.
* *
* @param func - The function to throttle. * @returns The throttled version of the input function.
* @param limit - The time limit in milliseconds.
*
* @returns The throttled function.
*/ */
export function throttle<T extends (...args: unknown[]) => void>(func: T, limit: number): T { export function throttle<T extends (...args: unknown[]) => void>(func: T, limit: number): T {
let inThrottle: boolean; let isThrottleActive: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) { return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) { if (!isThrottleActive) {
func.apply(this, args); func.apply(this, args);
inThrottle = true; isThrottleActive = true;
setTimeout(() => { setTimeout(() => {
inThrottle = false; isThrottleActive = false;
}, limit); }, limit);
} }
} as T; } as T;
} }
/** /**
* Creates throttled scroll handlers for navigating workspaces. * Creates throttled scroll handlers that navigate workspaces upon scrolling, respecting the configured scroll speed.
* *
* This function returns handlers for scrolling up and down through workspaces, throttled by the specified scroll speed. * @param scrollSpeed - The factor by which the scroll navigation is throttled.
* @param onlyActiveWorkspaces - Whether to only navigate among active (occupied) workspaces.
* *
* @param scrollSpeed - The scroll speed. * @returns An object containing two functions (`throttledScrollUp` and `throttledScrollDown`), both throttled.
* @param currentMonitorWorkspaces - The current monitor's workspaces variable.
* @param activeWorkspaces - Whether to consider only active workspaces.
*
* @returns The throttled scroll handlers.
*/ */
export const createThrottledScrollHandlers = ( export function initThrottledScrollHandlers(scrollSpeed: number): ThrottledScrollHandlers {
scrollSpeed: number,
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean = true,
): ThrottledScrollHandlers => {
const throttledScrollUp = throttle(() => { const throttledScrollUp = throttle(() => {
if (reverse_scroll.get()) { if (reverse_scroll.get()) {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces, ignored); goToPreviousWorkspace(ignored);
} else { } else {
goToNextWS(currentMonitorWorkspaces, activeWorkspaces, ignored); goToNextWorkspace(ignored);
} }
}, 200 / scrollSpeed); }, 200 / scrollSpeed);
const throttledScrollDown = throttle(() => { const throttledScrollDown = throttle(() => {
if (reverse_scroll.get()) { if (reverse_scroll.get()) {
goToNextWS(currentMonitorWorkspaces, activeWorkspaces, ignored); goToNextWorkspace(ignored);
} else { } else {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces, ignored); goToPreviousWorkspace(ignored);
} }
}, 200 / scrollSpeed); }, 200 / scrollSpeed);
return { throttledScrollUp, throttledScrollDown }; return { throttledScrollUp, throttledScrollDown };
}; }
/** /**
* Retrieves the workspaces to render. * Computes which workspace numbers should be rendered for a given monitor.
* *
* This function returns a list of workspace numbers to render based on the total workspaces, workspace list, rules, and monitor. * This function consolidates both active and all possible workspaces (based on rules),
* then filters them by the selected monitor if `isMonitorSpecific` is set to `true`.
* *
* @param totalWorkspaces - The total number of workspaces. * @param totalWorkspaces - The total number of workspaces (a fallback if workspace rules are not enforced).
* @param workspaceList - The list of workspaces. * @param workspaceInstances - A list of Hyprland workspace objects.
* @param workspaceRules - The workspace rules map. * @param workspaceMonitorRules - The map of monitor names to assigned workspace numbers.
* @param monitor - The monitor ID. * @param monitorId - The numeric identifier of the monitor.
* @param isMonitorSpecific - Whether the workspaces are monitor-specific. * @param isMonitorSpecific - If `true`, only include the workspaces that match this monitor.
* @param monitorList - The list of monitors. * @param hyprlandMonitorInstances - A list of Hyprland monitor objects.
* *
* @returns The list of workspace numbers to render. * @returns An array of workspace numbers that should be shown.
*/ */
export const getWorkspacesToRender = ( export function getWorkspacesToRender(
totalWorkspaces: number, totalWorkspaces: number,
workspaceList: AstalHyprland.Workspace[], workspaceInstances: AstalHyprland.Workspace[],
workspaceRules: WorkspaceMap, workspaceMonitorRules: WorkspaceMonitorMap,
monitor: number, monitorId: number,
isMonitorSpecific: boolean, isMonitorSpecific: boolean,
monitorList: AstalHyprland.Monitor[], hyprlandMonitorInstances: AstalHyprland.Monitor[],
): number[] => { ): number[] {
let allWorkspaces = range(totalWorkspaces || 8); let allPotentialWorkspaces = range(totalWorkspaces || 8);
const activeWorkspaces = workspaceList.map((ws) => ws.id); const allWorkspaceInstances = workspaceInstances ?? [];
const wsList = workspaceList ?? []; const activeWorkspaceIds = allWorkspaceInstances.map((workspaceInstance) => workspaceInstance.id);
const workspaceMonitorList = wsList.map((ws) => {
const monitorReferencesForActiveWorkspaces = allWorkspaceInstances.map((workspaceInstance) => {
return { return {
id: ws.monitor?.id || -1, id: workspaceInstance.monitor?.id ?? -1,
name: ws.monitor?.name || '', name: workspaceInstance.monitor?.name ?? '',
}; };
}); });
const curMonitor = const currentMonitorInstance =
monitorList.find((mon) => mon.id === monitor) || workspaceMonitorList.find((mon) => mon.id === monitor); hyprlandMonitorInstances.find((monitorObj) => monitorObj.id === monitorId) ||
monitorReferencesForActiveWorkspaces.find((monitorObj) => monitorObj.id === monitorId);
const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => { const allWorkspacesWithRules = Object.keys(workspaceMonitorRules).reduce(
return [...acc, ...workspaceRules[k]]; (accumulator: number[], monitorName: string) => {
}, []); return [...accumulator, ...workspaceMonitorRules[monitorName]];
},
[],
);
const activesForMonitor = activeWorkspaces.filter((w) => { const activeWorkspacesForCurrentMonitor = activeWorkspaceIds.filter((workspaceId) => {
if ( if (
curMonitor && currentMonitorInstance &&
Object.hasOwnProperty.call(workspaceRules, curMonitor.name) && Object.hasOwnProperty.call(workspaceMonitorRules, currentMonitorInstance.name) &&
workspacesWithRules.includes(w) allWorkspacesWithRules.includes(workspaceId)
) { ) {
return workspaceRules[curMonitor.name].includes(w); return workspaceMonitorRules[currentMonitorInstance.name].includes(workspaceId);
} }
return true; const metadataForWorkspace = allWorkspaceInstances.find((workspaceObj) => workspaceObj.id === workspaceId);
return metadataForWorkspace?.monitor?.id === monitorId;
}); });
if (isMonitorSpecific) { if (isMonitorSpecific) {
const workspacesInRange = range(totalWorkspaces).filter((ws) => { const validWorkspaceNumbers = range(totalWorkspaces).filter((workspaceNumber) => {
return getWorkspacesForMonitor(ws, workspaceRules, monitor, wsList, monitorList); return isWorkspaceValidForMonitor(
workspaceNumber,
workspaceMonitorRules,
monitorId,
allWorkspaceInstances,
hyprlandMonitorInstances,
);
}); });
allWorkspaces = [...new Set([...activesForMonitor, ...workspacesInRange])]; allPotentialWorkspaces = [...new Set([...activeWorkspacesForCurrentMonitor, ...validWorkspaceNumbers])];
} else { } else {
allWorkspaces = [...new Set([...allWorkspaces, ...activeWorkspaces])]; allPotentialWorkspaces = [...new Set([...allPotentialWorkspaces, ...activeWorkspaceIds])];
} }
return allWorkspaces.sort((a, b) => a - b); return allPotentialWorkspaces.filter((workspace) => !isWorkspaceIgnored(ignored, workspace)).sort((a, b) => a - b);
}; }
/** /**
* The workspace rules variable. * Subscribes to Hyprland service events related to workspaces to keep the local state updated.
* This variable holds the current workspace rules. *
* When certain events occur (like a configuration reload or a client being moved/added/removed),
* this function updates the workspace rules or toggles the `forceUpdater` variable to ensure
* that any dependent UI or logic is re-rendered or re-run.
*/ */
export const workspaceRules = Variable(getWorkspaceRules()); export function initWorkspaceEvents(): void {
/**
* 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', () => { hyprlandService.connect('config-reloaded', () => {
workspaceRules.set(getWorkspaceRules()); workspaceRules.set(getWorkspaceMonitorMap());
}); });
hyprlandService.connect('client-moved', () => { hyprlandService.connect('client-moved', () => {
@@ -358,9 +372,19 @@ export const setupConnections = (): void => {
hyprlandService.connect('client-removed', () => { hyprlandService.connect('client-removed', () => {
forceUpdater.set(!forceUpdater.get()); forceUpdater.set(!forceUpdater.get());
}); });
}; }
/**
* Throttled scroll handler functions for navigating workspaces.
*/
type ThrottledScrollHandlers = { type ThrottledScrollHandlers = {
/**
* Scroll up throttled handler.
*/
throttledScrollUp: () => void; throttledScrollUp: () => void;
/**
* Scroll down throttled handler.
*/
throttledScrollDown: () => void; throttledScrollDown: () => void;
}; };

View File

@@ -1,5 +1,5 @@
import options from 'src/options'; import options from 'src/options';
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces } from './helpers'; import { initThrottledScrollHandlers } from './helpers';
import { BarBoxChild } from 'src/lib/types/bar'; import { BarBoxChild } from 'src/lib/types/bar';
import { WorkspaceModule } from './workspaces'; import { WorkspaceModule } from './workspaces';
import { bind, Variable } from 'astal'; import { bind, Variable } from 'astal';
@@ -7,15 +7,9 @@ import { GtkWidget } from 'src/lib/types/widget';
import { Astal, Gdk } from 'astal/gtk3'; import { Astal, Gdk } from 'astal/gtk3';
import { isScrollDown, isScrollUp } from 'src/lib/utils'; import { isScrollDown, isScrollUp } from 'src/lib/utils';
const { workspaces, scroll_speed } = options.bar.workspaces; const { scroll_speed } = options.bar.workspaces;
const Workspaces = (monitor = -1): BarBoxChild => { const Workspaces = (monitor = -1): BarBoxChild => {
const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor));
workspaces.subscribe(() => {
currentMonitorWorkspaces.set(getCurrentMonitorWorkspaces(monitor));
});
const component = ( const component = (
<box className={'workspaces-box-container'}> <box className={'workspaces-box-container'}>
<WorkspaceModule monitor={monitor} /> <WorkspaceModule monitor={monitor} />
@@ -35,18 +29,15 @@ const Workspaces = (monitor = -1): BarBoxChild => {
self.disconnect(scrollHandlers); self.disconnect(scrollHandlers);
} }
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers( const { throttledScrollUp, throttledScrollDown } = initThrottledScrollHandlers(scroll_speed);
scroll_speed,
currentMonitorWorkspaces,
);
scrollHandlers = self.connect('scroll-event', (_: GtkWidget, event: Gdk.Event) => { scrollHandlers = self.connect('scroll-event', (_: GtkWidget, event: Gdk.Event) => {
if (isScrollUp(event)) { if (isScrollUp(event)) {
throttledScrollDown(); throttledScrollUp();
} }
if (isScrollDown(event)) { if (isScrollDown(event)) {
throttledScrollUp(); throttledScrollDown();
} }
}); });
}); });

View File

@@ -1,6 +1,6 @@
import { hyprlandService } from 'src/lib/constants/services'; import { hyprlandService } from 'src/lib/constants/services';
import options from 'src/options'; import options from 'src/options';
import { forceUpdater, getWorkspacesToRender, isWorkspaceIgnored, setupConnections, workspaceRules } from './helpers'; import { forceUpdater, getWorkspacesToRender, initWorkspaceEvents, workspaceRules } from './helpers';
import { getAppIcon, getWsColor, renderClassnames, renderLabel } from './helpers/utils'; import { getAppIcon, getWsColor, renderClassnames, renderLabel } from './helpers/utils';
import { ApplicationIcons, WorkspaceIconMap } from 'src/lib/types/workspace'; import { ApplicationIcons, WorkspaceIconMap } from 'src/lib/types/workspace';
import { bind, Variable } from 'astal'; import { bind, Variable } from 'astal';
@@ -30,7 +30,7 @@ const { available, active, occupied } = options.bar.workspaces.icons;
const { matugen } = options.theme; const { matugen } = options.theme;
const { smartHighlight } = options.theme.bar.buttons.workspaces; const { smartHighlight } = options.theme.bar.buttons.workspaces;
setupConnections(); initWorkspaceEvents();
export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element => { export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element => {
const boxChildren = Variable.derive( const boxChildren = Variable.derive(
@@ -98,10 +98,6 @@ export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element
); );
return workspacesToRender.map((wsId, index) => { return workspacesToRender.map((wsId, index) => {
if (isWorkspaceIgnored(ignored, wsId)) {
return <box />;
}
const appIcons = displayApplicationIcons const appIcons = displayApplicationIcons
? getAppIcon(wsId, appIconOncePerWorkspace, { ? getAppIcon(wsId, appIconOncePerWorkspace, {
iconMap: applicationIconMapping, iconMap: applicationIconMapping,

View File

@@ -1,4 +1,3 @@
import { BindableChild } from 'astal/gtk3/astalify';
import { audioService } from 'src/lib/constants/services'; import { audioService } from 'src/lib/constants/services';
import { SliderItem } from '../sliderItem/SliderItem'; import { SliderItem } from '../sliderItem/SliderItem';
import { ActiveDeviceMenu } from '..'; import { ActiveDeviceMenu } from '..';
@@ -21,5 +20,5 @@ export const ActiveDevices = (): JSX.Element => {
}; };
interface ActiveDeviceContainerProps { interface ActiveDeviceContainerProps {
children?: BindableChild | BindableChild[]; children?: JSX.Element[];
} }

View File

@@ -1,8 +1,7 @@
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { ActiveDevices } from './devices/index.js'; import { ActiveDevices } from './devices/index.js';
import Variable from 'astal/variable.js';
import { ActivePlaybacks } from './playbacks/index.js'; import { ActivePlaybacks } from './playbacks/index.js';
import { bind } from 'astal/binding.js'; import { bind, Variable } from 'astal';
import { isPrimaryClick } from 'src/lib/utils.js'; import { isPrimaryClick } from 'src/lib/utils.js';
export enum ActiveDeviceMenu { export enum ActiveDeviceMenu {

View File

@@ -1,5 +1,5 @@
import { audioService } from 'src/lib/constants/services.js'; import { audioService } from 'src/lib/constants/services.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { AudioDevice } from './Device'; import { AudioDevice } from './Device';
import { NotFoundButton } from './NotFoundButton'; import { NotFoundButton } from './NotFoundButton';

View File

@@ -1,5 +1,5 @@
import { audioService } from 'src/lib/constants/services.js'; import { audioService } from 'src/lib/constants/services.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { AudioDevice } from './Device'; import { AudioDevice } from './Device';
import { NotFoundButton } from './NotFoundButton'; import { NotFoundButton } from './NotFoundButton';

View File

@@ -1,7 +1,7 @@
import DropdownMenu from '../shared/dropdown/index.js'; import DropdownMenu from '../shared/dropdown/index.js';
import { VolumeSliders } from './active/index.js'; import { VolumeSliders } from './active/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { AvailableDevices } from './available/index.js'; import { AvailableDevices } from './available/index.js';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';

View File

@@ -1,5 +1,4 @@
import Variable from 'astal/variable.js'; import { bind, Variable } from 'astal';
import { bind } from 'astal/binding.js';
import { bluetoothService } from 'src/lib/constants/services.js'; import { bluetoothService } from 'src/lib/constants/services.js';
import { getAvailableBluetoothDevices, getConnectedBluetoothDevices } from './helpers.js'; import { getAvailableBluetoothDevices, getConnectedBluetoothDevices } from './helpers.js';
import { NoBluetoothDevices } from './NoBluetoothDevices.js'; import { NoBluetoothDevices } from './NoBluetoothDevices.js';

View File

@@ -2,7 +2,7 @@ import DropdownMenu from '../shared/dropdown/index.js';
import { BluetoothDevices } from './devices/index.js'; import { BluetoothDevices } from './devices/index.js';
import { Header } from './header/index.js'; import { Header } from './header/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';

View File

@@ -1,5 +1,3 @@
import { BindableChild } from 'astal/gtk3/astalify';
export const LeftSection = ({ children }: SectionProps): JSX.Element => { export const LeftSection = ({ children }: SectionProps): JSX.Element => {
return ( return (
<box className={'section left'} vertical expand> <box className={'section left'} vertical expand>
@@ -17,5 +15,5 @@ export const RightSection = ({ children }: SectionProps): JSX.Element => {
}; };
interface SectionProps { interface SectionProps {
children?: BindableChild | BindableChild[]; children?: JSX.Element | JSX.Element[];
} }

View File

@@ -5,8 +5,7 @@ import { Controls } from './controls/index.js';
import { Stats } from './stats/index.js'; import { Stats } from './stats/index.js';
import { Directories } from './directories/index.js'; import { Directories } from './directories/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { bind } from 'astal/binding.js'; import { bind, Variable } from 'astal';
import Variable from 'astal/variable.js';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';
const { controls, shortcuts, stats, directories } = options.menus.dashboard; const { controls, shortcuts, stats, directories } = options.menus.dashboard;

View File

@@ -1,4 +1,4 @@
import { bind, exec } from 'astal'; import { bind, GLib } from 'astal';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import options from 'src/options.js'; import options from 'src/options.js';
import { normalizePath, isAnImage } from 'src/lib/utils.js'; import { normalizePath, isAnImage } from 'src/lib/utils.js';
@@ -28,7 +28,8 @@ const ProfileName = (): JSX.Element => {
halign={Gtk.Align.CENTER} halign={Gtk.Align.CENTER}
label={bind(name).as((profileName) => { label={bind(name).as((profileName) => {
if (profileName === 'system') { if (profileName === 'system') {
return exec('bash -c whoami'); const username = GLib.get_user_name();
return username;
} }
return profileName; return profileName;
})} })}

View File

@@ -1,7 +1,7 @@
import { App } from 'astal/gtk3'; import { App } from 'astal/gtk3';
import powermenu from '../../power/helpers/actions.js'; import powermenu from '../../power/helpers/actions.js';
import { PowerOptions } from 'src/lib/types/options.js'; import { PowerOptions } from 'src/lib/types/options.js';
import { execAsync } from 'astal/process.js'; import { execAsync } from 'astal';
const { confirmation, shutdown, logout, sleep, reboot } = options.menus.dashboard.powermenu; const { confirmation, shutdown, logout, sleep, reboot } = options.menus.dashboard.powermenu;
/** /**

View File

@@ -1,5 +1,3 @@
import { BindableChild } from 'astal/gtk3/astalify';
export const LeftColumn = ({ isVisible, children }: LeftColumnProps): JSX.Element => { export const LeftColumn = ({ isVisible, children }: LeftColumnProps): JSX.Element => {
return ( return (
<box className={`card-button-section-container ${isVisible ? 'visible' : ''}`}> <box className={`card-button-section-container ${isVisible ? 'visible' : ''}`}>
@@ -26,9 +24,9 @@ export const RightColumn = ({ children }: RightColumnProps): JSX.Element => {
interface LeftColumnProps { interface LeftColumnProps {
isVisible?: boolean; isVisible?: boolean;
children?: BindableChild | BindableChild[]; children?: JSX.Element | JSX.Element[];
} }
interface RightColumnProps { interface RightColumnProps {
children?: BindableChild | BindableChild[]; children?: JSX.Element | JSX.Element[];
} }

View File

@@ -2,7 +2,7 @@ import DropdownMenu from '../shared/dropdown/index.js';
import { EnergyProfiles } from './profiles/index.js'; import { EnergyProfiles } from './profiles/index.js';
import { Brightness } from './brightness/index.js'; import { Brightness } from './brightness/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';

View File

@@ -1,6 +1,5 @@
import { getBackground } from './helpers.js'; import { getBackground } from './helpers.js';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { BindableChild } from 'astal/gtk3/astalify.js';
export const MediaContainer = ({ children }: MediaContainerProps): JSX.Element => { export const MediaContainer = ({ children }: MediaContainerProps): JSX.Element => {
return ( return (
@@ -19,5 +18,5 @@ export const MediaContainer = ({ children }: MediaContainerProps): JSX.Element =
}; };
interface MediaContainerProps { interface MediaContainerProps {
children?: BindableChild | BindableChild[]; children?: JSX.Element | JSX.Element[];
} }

View File

@@ -1,4 +1,4 @@
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import DropdownMenu from '../shared/dropdown/index.js'; import DropdownMenu from '../shared/dropdown/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { MediaContainer } from './components/MediaContainer.js'; import { MediaContainer } from './components/MediaContainer.js';

View File

@@ -2,7 +2,7 @@ import DropdownMenu from '../shared/dropdown/index.js';
import { Ethernet } from './ethernet/index.js'; import { Ethernet } from './ethernet/index.js';
import { Wifi } from './wifi/index.js'; import { Wifi } from './wifi/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { networkService } from 'src/lib/constants/services.js'; import { networkService } from 'src/lib/constants/services.js';
import { NoWifi } from './wifi/WirelessAPs/NoWifi.js'; import { NoWifi } from './wifi/WirelessAPs/NoWifi.js';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';

View File

@@ -3,9 +3,8 @@ import { Controls } from './controls/index.js';
import { NotificationsContainer } from './notification/index.js'; import { NotificationsContainer } from './notification/index.js';
import { NotificationPager } from './pager/index.js'; import { NotificationPager } from './pager/index.js';
import options from 'src/options.js'; import options from 'src/options.js';
import Variable from 'astal/variable.js';
import { handlePageBoundaries } from './helpers.js'; import { handlePageBoundaries } from './helpers.js';
import { bind } from 'astal/binding.js'; import { bind, Variable } from 'astal';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';
const { transition } = options.menus; const { transition } = options.menus;

View File

@@ -1,9 +1,8 @@
import options from 'src/options.js'; import options from 'src/options.js';
import { filterNotifications } from 'src/lib/shared/notifications.js'; import { filterNotifications } from 'src/lib/shared/notifications.js';
import AstalNotifd from 'gi://AstalNotifd?version=0.1'; import AstalNotifd from 'gi://AstalNotifd?version=0.1';
import Variable from 'astal/variable.js';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { bind } from 'astal/binding.js'; import { bind, Variable } from 'astal';
import { notifdService } from 'src/lib/constants/services.js'; import { notifdService } from 'src/lib/constants/services.js';
import { NotificationCard } from 'src/components/notifications/Notification.js'; import { NotificationCard } from 'src/components/notifications/Notification.js';
import { Placeholder } from './Placeholder'; import { Placeholder } from './Placeholder';

View File

@@ -4,7 +4,7 @@ import powermenu from './helpers/actions.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { isPrimaryClick } from 'src/lib/utils.js'; import { isPrimaryClick } from 'src/lib/utils.js';
import icons from 'src/lib/icons/icons.js'; import icons from 'src/lib/icons/icons.js';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { RevealerTransitionMap } from 'src/lib/constants/options.js'; import { RevealerTransitionMap } from 'src/lib/constants/options.js';

View File

@@ -1,7 +1,7 @@
import PopupWindow from '../shared/popup/index.js'; import PopupWindow from '../shared/popup/index.js';
import powermenu from './helpers/actions.js'; import powermenu from './helpers/actions.js';
import { App, Gtk } from 'astal/gtk3'; import { App, Gtk } from 'astal/gtk3';
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
export default (): JSX.Element => ( export default (): JSX.Element => (
<PopupWindow name="verification" transition="crossfade"> <PopupWindow name="verification" transition="crossfade">

View File

@@ -1,4 +1,4 @@
import { bind } from 'astal/binding.js'; import { bind } from 'astal';
import DropdownMenu from '../shared/dropdown/index.js'; import DropdownMenu from '../shared/dropdown/index.js';
import { PowerButton } from './button.js'; import { PowerButton } from './button.js';
import options from 'src/options.js'; import options from 'src/options.js';

View File

@@ -1,97 +1,215 @@
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import options from 'src/options'; import options from 'src/options';
import { bash } from 'src/lib/utils';
import { globalEventBoxes } from 'src/globals/dropdown'; import { globalEventBoxes } from 'src/globals/dropdown';
import { GLib } from 'astal'; import { GLib } from 'astal';
import { EventBox } from 'astal/gtk3/widget'; import { EventBox } from 'astal/gtk3/widget';
import { hyprlandService } from 'src/lib/constants/services';
const hyprland = AstalHyprland.get_default(); import AstalHyprland from 'gi://AstalHyprland?version=0.1';
const { location } = options.theme.bar; const { location } = options.theme.bar;
const { scalingPriority } = options; const { scalingPriority } = options;
export const calculateMenuPosition = async (pos: number[], windowName: string): Promise<void> => { /**
* Retrieves the dropdown EventBox widget from the global event boxes map using the provided window name.
*
* @param windowName - A string identifier for the window whose EventBox you want to retrieve.
* @returns The EventBox object if found; otherwise, `undefined`.
*/
function getDropdownEventBox(windowName: string): EventBox | undefined {
return globalEventBoxes.get()[windowName];
}
/**
* Finds and returns the currently focused Hyprland monitor object.
*
* @returns The focused Hyprland monitor, or `undefined` if no match is found.
*/
function getFocusedHyprlandMonitor(): AstalHyprland.Monitor | undefined {
const allMonitors = hyprlandService.get_monitors();
return allMonitors.find((monitor) => monitor.id === hyprlandService.focusedMonitor.id);
}
/**
* Computes the scaled monitor dimensions based on user configuration and environment variables.
*
* This function applies:
* 1. GDK scaling (from the `GDK_SCALE` environment variable).
* 2. Hyprland scaling (from the monitor's scale).
*
* The order in which scaling is applied depends on `scalingPriority`:
* - 'both': Apply GDK scale first, then Hyprland scale.
* - 'gdk': Apply GDK scale only.
* - Otherwise: Apply Hyprland scale only.
*
* @param width - The original width of the focused Hyprland monitor.
* @param height - The original height of the focused Hyprland monitor.
* @param monitorScaling - The scale factor reported by Hyprland for this monitor.
* @returns An object containing `adjustedWidth` and `adjustedHeight` after scaling is applied.
*/
function applyMonitorScaling(width: number, height: number, monitorScaling: number): MonitorScaling {
const gdkEnvScale = GLib.getenv('GDK_SCALE') || '1';
const userScalingPriority = scalingPriority.get();
let adjustedWidth = width;
let adjustedHeight = height;
if (userScalingPriority === 'both') {
const gdkScaleValue = parseFloat(gdkEnvScale);
adjustedWidth /= gdkScaleValue;
adjustedHeight /= gdkScaleValue;
adjustedWidth /= monitorScaling;
adjustedHeight /= monitorScaling;
} else if (/^\d+(\.\d+)?$/.test(gdkEnvScale) && userScalingPriority === 'gdk') {
const gdkScaleValue = parseFloat(gdkEnvScale);
adjustedWidth /= gdkScaleValue;
adjustedHeight /= gdkScaleValue;
} else {
adjustedWidth /= monitorScaling;
adjustedHeight /= monitorScaling;
}
return { adjustedWidth, adjustedHeight };
}
/**
* Corrects monitor dimensions if the monitor is rotated (vertical orientation),
* which requires swapping the width and height.
*
* @param monitorWidth - The monitor width after scaling.
* @param monitorHeight - The monitor height after scaling.
* @param isVertical - Whether the monitor transform indicates a vertical rotation.
* @returns The appropriately adjusted width and height.
*/
function adjustForVerticalTransform(
monitorWidth: number,
monitorHeight: number,
isVertical: boolean,
): TransformedDimensions {
if (!isVertical) {
return { finalWidth: monitorWidth, finalHeight: monitorHeight };
}
return { finalWidth: monitorHeight, finalHeight: monitorWidth };
}
/**
* Calculates the left and right margins required to place the dropdown in the correct position
* relative to the monitor width and the desired anchor X coordinate.
*
* @param monitorWidth - The width of the monitor (already scaled).
* @param dropdownWidth - The width of the dropdown widget.
* @param anchorX - The X coordinate (in scaled pixels) around which the dropdown should be placed.
* @returns An object containing `leftMargin` and `rightMargin`, ensuring they do not go below 0.
*/
function calculateHorizontalMargins(monitorWidth: number, dropdownWidth: number, anchorX: number): HorizontalMargins {
const minimumSpacing = 0;
let rightMarginSpacing = monitorWidth - dropdownWidth / 2;
rightMarginSpacing -= anchorX;
let leftMarginSpacing = monitorWidth - dropdownWidth - rightMarginSpacing;
if (rightMarginSpacing < minimumSpacing) {
rightMarginSpacing = minimumSpacing;
leftMarginSpacing = monitorWidth - dropdownWidth - minimumSpacing;
}
if (leftMarginSpacing < minimumSpacing) {
leftMarginSpacing = minimumSpacing;
rightMarginSpacing = monitorWidth - dropdownWidth - minimumSpacing;
}
return { leftMargin: leftMarginSpacing, rightMargin: rightMarginSpacing };
}
/**
* Positions the dropdown vertically based on the global bar location setting.
* If the bar is positioned at the top, the dropdown is placed at the top (margin_top = 0).
* Otherwise, it's placed at the bottom.
*
* @param dropdownEventBox - The EventBox representing the dropdown.
* @param monitorHeight - The scaled (and possibly swapped) monitor height.
* @param dropdownHeight - The height of the dropdown widget.
*/
function setVerticalPosition(dropdownEventBox: EventBox, monitorHeight: number, dropdownHeight: number): void {
if (location.get() === 'top') {
dropdownEventBox.set_margin_top(0);
dropdownEventBox.set_margin_bottom(monitorHeight);
} else {
dropdownEventBox.set_margin_bottom(0);
dropdownEventBox.set_margin_top(monitorHeight - dropdownHeight);
}
}
/**
* Adjusts the position of a dropdown menu (event box) based on the focused monitor, scaling preferences,
* and the bar location setting. It ensures the dropdown is accurately placed either at the top or bottom
* of the screen within monitor boundaries, respecting both GDK scaling and Hyprland scaling.
*
* @param positionCoordinates - An array of `[x, y]` values representing the anchor position at which
* the dropdown should ideally appear (only the X coordinate is used here).
* @param windowName - A string that identifies the window in the globalEventBoxes map.
*
* @returns A Promise that resolves once the dropdown position is fully calculated and set.
*/
export const calculateMenuPosition = async (positionCoordinates: number[], windowName: string): Promise<void> => {
try { try {
const self = globalEventBoxes.get()[windowName] as EventBox; const dropdownEventBox = getDropdownEventBox(windowName);
const curHyprlandMonitor = hyprland.get_monitors().find((m) => m.id === hyprland.focusedMonitor.id); if (!dropdownEventBox) {
const dropdownWidth = self.get_child()?.get_allocation().width ?? 0;
const dropdownHeight = self.get_child()?.get_allocation().height ?? 0;
let hyprScaling = 1;
const monitorInfo = await bash('hyprctl monitors -j');
const parsedMonitorInfo = JSON.parse(monitorInfo);
const foundMonitor = parsedMonitorInfo.find(
(monitor: AstalHyprland.Monitor) => monitor.id === hyprland.focusedMonitor.id,
);
hyprScaling = foundMonitor?.scale || 1;
let monWidth = curHyprlandMonitor?.width;
let monHeight = curHyprlandMonitor?.height;
if (monWidth === undefined || monHeight === undefined || hyprScaling === undefined) {
return; return;
} }
// If GDK Scaling is applied, then get divide width by scaling const focusedHyprlandMonitor = getFocusedHyprlandMonitor();
// to get the proper coordinates.
// Ex: On a 2860px wide monitor... if scaling is set to 2, then the right
// end of the monitor is the 1430th pixel.
const gdkScale = GLib.getenv('GDK_SCALE') || '1';
if (scalingPriority.get() === 'both') { if (!focusedHyprlandMonitor) {
const scale = parseFloat(gdkScale); return;
monWidth = monWidth / scale;
monHeight = monHeight / scale;
monWidth = monWidth / hyprScaling;
monHeight = monHeight / hyprScaling;
} else if (/^\d+(.\d+)?$/.test(gdkScale) && scalingPriority.get() === 'gdk') {
const scale = parseFloat(gdkScale);
monWidth = monWidth / scale;
monHeight = monHeight / scale;
} else {
monWidth = monWidth / hyprScaling;
monHeight = monHeight / hyprScaling;
} }
// If monitor is vertical (transform = 1 || 3) swap height and width const dropdownWidth = dropdownEventBox.get_child()?.get_allocation().width ?? 0;
const isVertical = curHyprlandMonitor?.transform !== undefined ? curHyprlandMonitor.transform % 2 !== 0 : false; const dropdownHeight = dropdownEventBox.get_child()?.get_allocation().height ?? 0;
if (isVertical) { const monitorScaling = focusedHyprlandMonitor.scale || 1;
[monWidth, monHeight] = [monHeight, monWidth]; const { width: rawMonitorWidth, height: rawMonitorHeight, transform } = focusedHyprlandMonitor;
if (!rawMonitorWidth || !rawMonitorHeight) {
return;
} }
let marginRight = monWidth - dropdownWidth / 2; const { adjustedWidth, adjustedHeight } = applyMonitorScaling(
marginRight = marginRight - pos[0]; rawMonitorWidth,
let marginLeft = monWidth - dropdownWidth - marginRight; rawMonitorHeight,
monitorScaling,
);
const minimumMargin = 0; const isVertical = transform !== undefined ? transform % 2 !== 0 : false;
const { finalWidth, finalHeight } = adjustForVerticalTransform(adjustedWidth, adjustedHeight, isVertical);
if (marginRight < minimumMargin) { const { leftMargin, rightMargin } = calculateHorizontalMargins(
marginRight = minimumMargin; finalWidth,
marginLeft = monWidth - dropdownWidth - minimumMargin; dropdownWidth,
} positionCoordinates[0],
);
if (marginLeft < minimumMargin) { dropdownEventBox.set_margin_left(leftMargin);
marginLeft = minimumMargin; dropdownEventBox.set_margin_right(rightMargin);
marginRight = monWidth - dropdownWidth - minimumMargin;
}
self.set_margin_left(marginLeft); setVerticalPosition(dropdownEventBox, finalHeight, dropdownHeight);
self.set_margin_right(marginRight); } catch (caughtError) {
console.error(`Error getting menu position: ${caughtError}`);
if (location.get() === 'top') {
self.set_margin_top(0);
self.set_margin_bottom(monHeight);
} else {
self.set_margin_bottom(0);
self.set_margin_top(monHeight - dropdownHeight);
}
} catch (error) {
console.error(`Error getting menu position: ${error}`);
} }
}; };
type HorizontalMargins = {
leftMargin: number;
rightMargin: number;
};
type MonitorScaling = {
adjustedWidth: number;
adjustedHeight: number;
};
type TransformedDimensions = {
finalWidth: number;
finalHeight: number;
};

View File

@@ -1,6 +1,6 @@
import AstalNotifd from 'gi://AstalNotifd?version=0.1'; import AstalNotifd from 'gi://AstalNotifd?version=0.1';
import options from 'src/options.js'; import options from 'src/options.js';
import { GLib } from 'astal/gobject.js'; import { GLib } from 'astal';
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import { getNotificationIcon } from 'src/globals/notification.js'; import { getNotificationIcon } from 'src/globals/notification.js';
import { notifHasImg } from './helpers'; import { notifHasImg } from './helpers';

View File

@@ -1,8 +1,7 @@
import { hyprlandService } from 'src/lib/constants/services.js'; import { hyprlandService } from 'src/lib/constants/services.js';
import options from 'src/options.js'; import options from 'src/options.js';
import { getPosition } from 'src/lib/utils.js'; import { getPosition } from 'src/lib/utils.js';
import Variable from 'astal/variable.js'; import { bind, Variable } from 'astal';
import { bind } from 'astal/binding.js';
import { trackActiveMonitor, trackAutoTimeout, trackPopupNotifications } from './helpers.js'; import { trackActiveMonitor, trackAutoTimeout, trackPopupNotifications } from './helpers.js';
import { Astal } from 'astal/gtk3'; import { Astal } from 'astal/gtk3';
import { NotificationCard } from './Notification.js'; import { NotificationCard } from './Notification.js';

View File

@@ -140,12 +140,71 @@ export const BarSettings = (): JSX.Element => {
{/* Workspaces Section */} {/* Workspaces Section */}
<Header title="Workspaces" /> <Header title="Workspaces" />
<Option opt={options.theme.bar.buttons.workspaces.enableBorder} title="Button Border" type="boolean" /> <Option opt={options.theme.bar.buttons.workspaces.enableBorder} title="Button Border" type="boolean" />
<Option
opt={options.bar.workspaces.monitorSpecific}
title="Monitor Specific"
subtitle="Only workspaces of the monitor are shown."
type="boolean"
/>
<Option opt={options.bar.workspaces.show_icons} title="Show Workspace Icons" type="boolean" />
<Option opt={options.bar.workspaces.show_numbered} title="Show Workspace Numbers" type="boolean" />
<Option
opt={options.bar.workspaces.workspaceMask}
title="Zero-Based Workspace Numbers"
subtitle={
'Start all workspace numbers from 0 on each monitor.\n' +
"Requires 'Show Workspace Numbers' to be enabled."
}
type="boolean"
/>
<Option
opt={options.bar.workspaces.showWsIcons}
title="Map Workspaces to Icons"
subtitle="https://hyprpanel.com/configuration/panel.html#show-workspace-icons"
type="boolean"
/>
<Option
opt={options.bar.workspaces.showApplicationIcons}
title="Map Workspaces to Application Icons"
subtitle="Requires 'Map Workspace to Icons' enabled. See docs."
type="boolean"
/>
<Option
opt={options.bar.workspaces.applicationIconOncePerWorkspace}
title="Hide Duplicate App Icons"
type="boolean"
/>
<Option <Option
opt={options.bar.workspaces.showAllActive} opt={options.bar.workspaces.showAllActive}
title="Mark Active Workspace On All Monitors" title="Mark Active Workspace On All Monitors"
subtitle="Marks the currently active workspace on each monitor." subtitle="Marks the currently active workspace on each monitor."
type="boolean" type="boolean"
/> />
<Option
opt={options.bar.workspaces.numbered_active_indicator}
title="Numbered Workspace Identifier"
subtitle="Only applicable if Workspace Numbers are enabled"
type="enum"
enums={['underline', 'highlight', 'color']}
/>
<Option
opt={options.theme.bar.buttons.workspaces.smartHighlight}
title="Smart Highlight"
subtitle="Automatically determines highlight color for mapped icons."
type="boolean"
/>
<Option
opt={options.theme.bar.buttons.workspaces.numbered_active_highlight_border}
title="Highlight Radius"
subtitle="Only applicable if Workspace Numbers are enabled"
type="string"
/>
<Option
opt={options.theme.bar.buttons.workspaces.numbered_active_highlight_padding}
title="Highlight Padding"
subtitle="Only applicable if Workspace Numbers are enabled"
type="string"
/>
<Option <Option
opt={options.theme.bar.buttons.workspaces.pill.radius} opt={options.theme.bar.buttons.workspaces.pill.radius}
title="Pill Radius" title="Pill Radius"
@@ -176,53 +235,9 @@ export const BarSettings = (): JSX.Element => {
subtitle="Only applicable to numbered workspaces and mapped icons. Adjust carefully." subtitle="Only applicable to numbered workspaces and mapped icons. Adjust carefully."
type="string" type="string"
/> />
<Option opt={options.bar.workspaces.show_icons} title="Show Workspace Icons" type="boolean" />
<Option opt={options.bar.workspaces.icons.available} title="Workspace Available" type="string" /> <Option opt={options.bar.workspaces.icons.available} title="Workspace Available" type="string" />
<Option opt={options.bar.workspaces.icons.active} title="Workspace Active" type="string" /> <Option opt={options.bar.workspaces.icons.active} title="Workspace Active" type="string" />
<Option opt={options.bar.workspaces.icons.occupied} title="Workspace Occupied" type="string" /> <Option opt={options.bar.workspaces.icons.occupied} title="Workspace Occupied" type="string" />
<Option opt={options.bar.workspaces.show_numbered} title="Show Workspace Numbers" type="boolean" />
<Option
opt={options.bar.workspaces.numbered_active_indicator}
title="Numbered Workspace Identifier"
subtitle="Only applicable if Workspace Numbers are enabled"
type="enum"
enums={['underline', 'highlight', 'color']}
/>
<Option
opt={options.theme.bar.buttons.workspaces.smartHighlight}
title="Smart Highlight"
subtitle="Automatically determines highlight color for mapped icons."
type="boolean"
/>
<Option
opt={options.theme.bar.buttons.workspaces.numbered_active_highlight_border}
title="Highlight Radius"
subtitle="Only applicable if Workspace Numbers are enabled"
type="string"
/>
<Option
opt={options.theme.bar.buttons.workspaces.numbered_active_highlight_padding}
title="Highlight Padding"
subtitle="Only applicable if Workspace Numbers are enabled"
type="string"
/>
<Option
opt={options.bar.workspaces.showWsIcons}
title="Map Workspaces to Icons"
subtitle="https://hyprpanel.com/configuration/panel.html#show-workspace-icons"
type="boolean"
/>
<Option
opt={options.bar.workspaces.showApplicationIcons}
title="Map Workspaces to Application Icons"
subtitle="Requires 'Map Workspace to Icons' enabled. See docs."
type="boolean"
/>
<Option
opt={options.bar.workspaces.applicationIconOncePerWorkspace}
title="Hide Duplicate App Icons"
type="boolean"
/>
<Option <Option
opt={options.bar.workspaces.applicationIconMap} opt={options.bar.workspaces.applicationIconMap}
title="App Icon Mappings" title="App Icon Mappings"
@@ -254,28 +269,10 @@ export const BarSettings = (): JSX.Element => {
/> />
<Option <Option
opt={options.bar.workspaces.workspaces} opt={options.bar.workspaces.workspaces}
title="Total Workspaces" title="Persistent Workspaces"
subtitle="Minimum number of workspaces to always show." subtitle="Requires workspace rules to be defined if 'Monitor Specific' is selected."
type="number" type="number"
/> />
<Option
opt={options.bar.workspaces.monitorSpecific}
title="Monitor Specific"
subtitle="Only workspaces of the monitor are shown."
type="boolean"
/>
<Option
opt={options.bar.workspaces.hideUnoccupied}
title="Hide Unoccupied"
subtitle="Only show occupied or active workspaces"
type="boolean"
/>
<Option
opt={options.bar.workspaces.workspaceMask}
title="Mask Workspace Numbers On Monitors"
subtitle="For monitor-specific numbering"
type="boolean"
/>
<Option <Option
opt={options.bar.workspaces.reverse_scroll} opt={options.bar.workspaces.reverse_scroll}
title="Invert Scroll" title="Invert Scroll"

View File

@@ -1,5 +1,5 @@
import { Gtk } from 'astal/gtk3'; import { Gtk } from 'astal/gtk3';
import FileChooserButton from 'src/components/shared/FileChooseButton'; import FileChooserButton from 'src/components/shared/FileChooserButton';
import { Opt } from 'src/lib/option'; import { Opt } from 'src/lib/option';
const handleFileSet = const handleFileSet =

View File

@@ -1,4 +1,4 @@
import FileChooserButton from 'src/components/shared/FileChooseButton'; import FileChooserButton from 'src/components/shared/FileChooserButton';
import { Opt } from 'src/lib/option'; import { Opt } from 'src/lib/option';
import Wallpaper from 'src/services/Wallpaper'; import Wallpaper from 'src/services/Wallpaper';

View File

@@ -1,6 +1,7 @@
import { EventBox } from 'astal/gtk3/widget';
import Variable from 'astal/variable'; import Variable from 'astal/variable';
type GlobalEventBoxes = { type GlobalEventBoxes = {
[key: string]: unknown; [key: string]: EventBox;
}; };
export const globalEventBoxes: Variable<GlobalEventBoxes> = Variable({}); export const globalEventBoxes: Variable<GlobalEventBoxes> = Variable({});

View File

@@ -1,19 +1,18 @@
import { execAsync } from 'astal';
import { hyprlandService } from '../constants/services'; import { hyprlandService } from '../constants/services';
const floatSettingsDialog = (): void => { const floatSettingsDialog = (): void => {
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^(hyprpanel-settings)$"']); hyprlandService.message(`keyword windowrulev2 float, title:^(hyprpanel-settings)$`);
hyprlandService.connect('config-reloaded', () => { hyprlandService.connect('config-reloaded', () => {
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^(hyprpanel-settings)$"']); hyprlandService.message(`keyword windowrulev2 float, title:^(hyprpanel-settings)$`);
}); });
}; };
const floatFilePicker = (): void => { const floatFilePicker = (): void => {
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^((Save|Import) Hyprpanel.*)$"']); hyprlandService.message(`keyword windowrulev2 float, title:^((Save|Import) Hyprpanel.*)$`);
hyprlandService.connect('config-reloaded', () => { hyprlandService.connect('config-reloaded', () => {
execAsync(['bash', '-c', 'hyprctl keyword windowrulev2 "float, title:^((Save|Import) Hyprpanel.*)$"']); hyprlandService.message(`keyword windowrulev2 float, title:^((Save|Import) Hyprpanel.*)$`);
}); });
}; };

View File

@@ -3,11 +3,10 @@ import { Astal, Gtk } from 'astal/gtk3';
import { WindowProps } from 'astal/gtk3/widget'; import { WindowProps } from 'astal/gtk3/widget';
import { Opt } from '../option'; import { Opt } from '../option';
import { Binding } from 'astal'; import { Binding } from 'astal';
import { BindableChild } from 'astal/gtk3/astalify';
export interface DropdownMenuProps extends WindowProps { export interface DropdownMenuProps extends WindowProps {
name: string; name: string;
child?: BindableChild | BindableChild[]; child?: JSX.Element | JSX.Element[];
layout?: string; layout?: string;
transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>; transition?: Gtk.RevealerTransitionType | Binding<Gtk.RevealerTransitionType>;
exclusivity?: Astal.Exclusivity; exclusivity?: Astal.Exclusivity;

View File

@@ -5,7 +5,7 @@ import { Gtk } from 'astal/gtk3';
export type PopupWindowProps = { export type PopupWindowProps = {
name: string; name: string;
child?: BindableChild | BindableChild[]; child?: JSX.Element | JSX.Element[];
layout?: Layouts; layout?: Layouts;
transition?: Transition | Binding<Transition>; transition?: Transition | Binding<Transition>;
exclusivity?: Exclusivity; exclusivity?: Exclusivity;

View File

@@ -3,7 +3,7 @@ export type WorkspaceRule = {
monitor: string; monitor: string;
}; };
export type WorkspaceMap = { export type WorkspaceMonitorMap = {
[key: string]: number[]; [key: string]: number[];
}; };

View File

@@ -959,7 +959,6 @@ const options = mkOptions(CONFIG, {
workspaces: opt(5), workspaces: opt(5),
spacing: opt(1), spacing: opt(1),
monitorSpecific: opt(true), monitorSpecific: opt(true),
hideUnoccupied: opt(true),
workspaceMask: opt(false), workspaceMask: opt(false),
reverse_scroll: opt(false), reverse_scroll: opt(false),
scroll_speed: opt(5), scroll_speed: opt(5),

View File

@@ -3,6 +3,7 @@ import { dependencies, sh } from '../lib/utils';
import options from '../options'; import options from '../options';
import { execAsync } from 'astal/process'; import { execAsync } from 'astal/process';
import { monitorFile } from 'astal/file'; import { monitorFile } from 'astal/file';
import { hyprlandService } from 'src/lib/constants/services';
const WP = `${GLib.get_home_dir()}/.config/background`; const WP = `${GLib.get_home_dir()}/.config/background`;
@@ -14,8 +15,8 @@ class Wallpaper extends GObject.Object {
#wallpaper(): void { #wallpaper(): void {
if (!dependencies('swww')) return; if (!dependencies('swww')) return;
sh('hyprctl cursorpos') try {
.then((pos) => { const cursorPosition = hyprlandService.message('cursorpos');
const transitionCmd = [ const transitionCmd = [
'swww', 'swww',
'img', 'img',
@@ -27,7 +28,7 @@ class Wallpaper extends GObject.Object {
'--transition-fps', '--transition-fps',
'30', '30',
'--transition-pos', '--transition-pos',
pos.replace(' ', ''), cursorPosition.replace(' ', ''),
WP, WP,
].join(' '); ].join(' ');
@@ -39,10 +40,9 @@ class Wallpaper extends GObject.Object {
.catch((err) => { .catch((err) => {
console.error('Error setting wallpaper:', err); console.error('Error setting wallpaper:', err);
}); });
}) } catch (err) {
.catch((err) => {
console.error('Error getting cursor position:', err); console.error('Error getting cursor position:', err);
}); }
} }
async #setWallpaper(path: string): Promise<void> { async #setWallpaper(path: string): Promise<void> {

View File

@@ -14,7 +14,7 @@
"alwaysStrict": true, "alwaysStrict": true,
"noImplicitThis": true, "noImplicitThis": true,
"baseUrl": ".", "baseUrl": ".",
"typeRoots": ["types", "src/lib/types"], "typeRoots": ["src/lib/types"],
"skipLibCheck": true, "skipLibCheck": true,
"types": [], "types": [],
"experimentalDecorators": true, "experimentalDecorators": true,