Workspaces now show up on their appropriate monitors. (#681)
* Workspaces now show up on their appropriate monitors. * Fixed undefined rules showing up.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { bind, exec } from 'astal';
|
||||
import { bind, GLib } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
import options from 'src/options.js';
|
||||
import { normalizePath, isAnImage } from 'src/lib/utils.js';
|
||||
@@ -28,7 +28,8 @@ const ProfileName = (): JSX.Element => {
|
||||
halign={Gtk.Align.CENTER}
|
||||
label={bind(name).as((profileName) => {
|
||||
if (profileName === 'system') {
|
||||
return exec('bash -c whoami');
|
||||
const username = GLib.get_user_name();
|
||||
return username;
|
||||
}
|
||||
return profileName;
|
||||
})}
|
||||
|
||||
@@ -1,97 +1,215 @@
|
||||
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
|
||||
|
||||
import options from 'src/options';
|
||||
import { bash } from 'src/lib/utils';
|
||||
import { globalEventBoxes } from 'src/globals/dropdown';
|
||||
import { GLib } from 'astal';
|
||||
import { EventBox } from 'astal/gtk3/widget';
|
||||
|
||||
const hyprland = AstalHyprland.get_default();
|
||||
import { hyprlandService } from 'src/lib/constants/services';
|
||||
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
|
||||
|
||||
const { location } = options.theme.bar;
|
||||
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 {
|
||||
const self = globalEventBoxes.get()[windowName] as EventBox;
|
||||
const dropdownEventBox = getDropdownEventBox(windowName);
|
||||
|
||||
const curHyprlandMonitor = hyprland.get_monitors().find((m) => m.id === hyprland.focusedMonitor.id);
|
||||
|
||||
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) {
|
||||
if (!dropdownEventBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If GDK Scaling is applied, then get divide width by scaling
|
||||
// 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';
|
||||
const focusedHyprlandMonitor = getFocusedHyprlandMonitor();
|
||||
|
||||
if (scalingPriority.get() === 'both') {
|
||||
const scale = parseFloat(gdkScale);
|
||||
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 (!focusedHyprlandMonitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If monitor is vertical (transform = 1 || 3) swap height and width
|
||||
const isVertical = curHyprlandMonitor?.transform !== undefined ? curHyprlandMonitor.transform % 2 !== 0 : false;
|
||||
const dropdownWidth = dropdownEventBox.get_child()?.get_allocation().width ?? 0;
|
||||
const dropdownHeight = dropdownEventBox.get_child()?.get_allocation().height ?? 0;
|
||||
|
||||
if (isVertical) {
|
||||
[monWidth, monHeight] = [monHeight, monWidth];
|
||||
const monitorScaling = focusedHyprlandMonitor.scale || 1;
|
||||
const { width: rawMonitorWidth, height: rawMonitorHeight, transform } = focusedHyprlandMonitor;
|
||||
|
||||
if (!rawMonitorWidth || !rawMonitorHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
let marginRight = monWidth - dropdownWidth / 2;
|
||||
marginRight = marginRight - pos[0];
|
||||
let marginLeft = monWidth - dropdownWidth - marginRight;
|
||||
const { adjustedWidth, adjustedHeight } = applyMonitorScaling(
|
||||
rawMonitorWidth,
|
||||
rawMonitorHeight,
|
||||
monitorScaling,
|
||||
);
|
||||
|
||||
const minimumMargin = 0;
|
||||
const isVertical = transform !== undefined ? transform % 2 !== 0 : false;
|
||||
const { finalWidth, finalHeight } = adjustForVerticalTransform(adjustedWidth, adjustedHeight, isVertical);
|
||||
|
||||
if (marginRight < minimumMargin) {
|
||||
marginRight = minimumMargin;
|
||||
marginLeft = monWidth - dropdownWidth - minimumMargin;
|
||||
}
|
||||
const { leftMargin, rightMargin } = calculateHorizontalMargins(
|
||||
finalWidth,
|
||||
dropdownWidth,
|
||||
positionCoordinates[0],
|
||||
);
|
||||
|
||||
if (marginLeft < minimumMargin) {
|
||||
marginLeft = minimumMargin;
|
||||
marginRight = monWidth - dropdownWidth - minimumMargin;
|
||||
}
|
||||
dropdownEventBox.set_margin_left(leftMargin);
|
||||
dropdownEventBox.set_margin_right(rightMargin);
|
||||
|
||||
self.set_margin_left(marginLeft);
|
||||
self.set_margin_right(marginRight);
|
||||
|
||||
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}`);
|
||||
setVerticalPosition(dropdownEventBox, finalHeight, dropdownHeight);
|
||||
} catch (caughtError) {
|
||||
console.error(`Error getting menu position: ${caughtError}`);
|
||||
}
|
||||
};
|
||||
|
||||
type HorizontalMargins = {
|
||||
leftMargin: number;
|
||||
rightMargin: number;
|
||||
};
|
||||
|
||||
type MonitorScaling = {
|
||||
adjustedWidth: number;
|
||||
adjustedHeight: number;
|
||||
};
|
||||
|
||||
type TransformedDimensions = {
|
||||
finalWidth: number;
|
||||
finalHeight: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user