Merge branch 'master' into master

This commit is contained in:
Ben
2024-12-25 00:08:32 +10:00
committed by GitHub
23 changed files with 188 additions and 78 deletions

View File

@@ -49,7 +49,6 @@
pkgs.glib pkgs.glib
pkgs.bluez-tools pkgs.bluez-tools
pkgs.grimblast pkgs.grimblast
pkgs.gpu-screen-recorder
pkgs.brightnessctl pkgs.brightnessctl
pkgs.gnome-bluetooth pkgs.gnome-bluetooth
(pkgs.python3.withPackages (python-pkgs: with python-pkgs; [ (pkgs.python3.withPackages (python-pkgs: with python-pkgs; [
@@ -67,7 +66,7 @@
pkgs.gvfs pkgs.gvfs
pkgs.swww pkgs.swww
pkgs.pywal pkgs.pywal
]; ] ++ (nixpkgs.lib.optionals (system == "x86_64-linux") [pkgs.gpu-screen-recorder]);
}; };
}); });

View File

@@ -3,14 +3,19 @@
check_arch_updates() { check_arch_updates() {
official_updates=0 official_updates=0
aur_updates=0 aur_updates=0
if command -v paru &> /dev/null; then
aur_helper="paru"
else
aur_helper="yay"
fi
if [ "$1" = "-y" ]; then if [ "$1" = "-y" ]; then
aur_updates=$(yay -Qum 2>/dev/null | wc -l) aur_updates=$($aur_helper -Qum 2>/dev/null | wc -l)
elif [ "$1" = "-p" ]; then elif [ "$1" = "-p" ]; then
official_updates=$(checkupdates 2>/dev/null | wc -l) official_updates=$(checkupdates 2>/dev/null | wc -l)
else else
official_updates=$(checkupdates 2>/dev/null | wc -l) official_updates=$(checkupdates 2>/dev/null | wc -l)
aur_updates=$(yay -Qum 2>/dev/null | wc -l) aur_updates=$($aur_helper -Qum 2>/dev/null | wc -l)
fi fi
total_updates=$((official_updates + aur_updates)) total_updates=$((official_updates + aur_updates))

View File

@@ -19,6 +19,8 @@ const {
rateUnit, rateUnit,
dynamicIcon, dynamicIcon,
icon, icon,
networkInLabel,
networkOutLabel,
round, round,
leftClick, leftClick,
rightClick, rightClick,
@@ -47,11 +49,11 @@ export const Netstat = (): BarBoxChild => {
const renderNetworkLabel = (lblType: NetstatLabelType, networkService: NetworkResourceData): string => { const renderNetworkLabel = (lblType: NetstatLabelType, networkService: NetworkResourceData): string => {
switch (lblType) { switch (lblType) {
case 'in': case 'in':
return ` ${networkService.in}`; return `${networkInLabel.get()} ${networkService.in}`;
case 'out': case 'out':
return ` ${networkService.out}`; return `${networkOutLabel.get()} ${networkService.out}`;
default: default:
return `${networkService.in} ${networkService.out}`; return `${networkInLabel.get()} ${networkService.in} ${networkOutLabel.get()} ${networkService.out}`;
} }
}; };

View File

@@ -10,6 +10,7 @@ const {
updateCommand, updateCommand,
label, label,
padZero, padZero,
autoHide,
pollingInterval, pollingInterval,
icon, icon,
leftClick, leftClick,
@@ -21,6 +22,7 @@ const {
const pendingUpdates: Variable<string> = Variable('0'); const pendingUpdates: Variable<string> = Variable('0');
const postInputUpdater = Variable(true); const postInputUpdater = Variable(true);
const isVis = Variable(!autoHide.get());
const processUpdateCount = (updateCount: string): string => { const processUpdateCount = (updateCount: string): string => {
if (!padZero.get()) return updateCount; if (!padZero.get()) return updateCount;
@@ -37,9 +39,14 @@ const updatesPoller = new BashPoller<string, []>(
updatesPoller.initialize('updates'); updatesPoller.initialize('updates');
Variable.derive([bind(autoHide)], (autoHideModule) => {
isVis.set(!autoHideModule || (autoHideModule && parseFloat(pendingUpdates.get()) > 0));
});
const updatesIcon = Variable.derive( const updatesIcon = Variable.derive(
[bind(icon.pending), bind(icon.updated), bind(pendingUpdates)], [bind(icon.pending), bind(icon.updated), bind(pendingUpdates)],
(pendingIcon, updatedIcon, pUpdates) => { (pendingIcon, updatedIcon, pUpdates) => {
isVis.set(!autoHide.get() || (autoHide.get() && parseFloat(pUpdates) > 0));
return parseFloat(pUpdates) === 0 ? updatedIcon : pendingIcon; return parseFloat(pUpdates) === 0 ? updatedIcon : pendingIcon;
}, },
); );
@@ -49,6 +56,7 @@ export const Updates = (): BarBoxChild => {
textIcon: updatesIcon(), textIcon: updatesIcon(),
tooltipText: bind(pendingUpdates).as((v) => `${v} updates available`), tooltipText: bind(pendingUpdates).as((v) => `${v} updates available`),
boxClass: 'updates', boxClass: 'updates',
isVis: isVis,
label: bind(pendingUpdates), label: bind(pendingUpdates),
showLabelBinding: bind(label), showLabelBinding: bind(label),
props: { props: {

View File

@@ -162,7 +162,7 @@ const navigateWorkspace = (
while (attempts < workspacesList.length) { while (attempts < workspacesList.length) {
const targetWS = workspacesList[newIndex]; const targetWS = workspacesList[newIndex];
if (!isWorkspaceIgnored(ignoredWorkspaces, targetWS)) { if (!isWorkspaceIgnored(ignoredWorkspaces, targetWS)) {
hyprlandService.message_async(`dispatch workspace ${targetWS}`); hyprlandService.dispatch('workspace', targetWS.toString());
return; return;
} }
newIndex = (newIndex + step + workspacesList.length) % workspacesList.length; newIndex = (newIndex + step + workspacesList.length) % workspacesList.length;

View File

@@ -1,10 +1,11 @@
import options from 'src/options'; import options from 'src/options';
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces } from './helpers'; import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces } from './helpers';
import { BarBoxChild, SelfButton } 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';
import { GtkWidget } from 'src/lib/types/widget'; import { GtkWidget } from 'src/lib/types/widget';
import { Gdk } from 'astal/gtk3'; import { Astal, Gdk } from 'astal/gtk3';
import { isScrollDown, isScrollUp } from 'src/lib/utils';
const { workspaces, scroll_speed } = options.bar.workspaces; const { workspaces, scroll_speed } = options.bar.workspaces;
@@ -27,23 +28,27 @@ const Workspaces = (monitor = -1): BarBoxChild => {
boxClass: 'workspaces', boxClass: 'workspaces',
isBox: true, isBox: true,
props: { props: {
setup: (self: SelfButton): void => { setup: (self: Astal.EventBox): void => {
let scrollHandlers: number;
Variable.derive([bind(scroll_speed)], (scroll_speed) => { Variable.derive([bind(scroll_speed)], (scroll_speed) => {
if (scrollHandlers) {
self.disconnect(scrollHandlers);
}
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers( const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(
scroll_speed, scroll_speed,
currentMonitorWorkspaces, currentMonitorWorkspaces,
); );
const scrollHandlers = self.connect('scroll-event', (_: GtkWidget, event: Gdk.Event) => { scrollHandlers = self.connect('scroll-event', (_: GtkWidget, event: Gdk.Event) => {
const eventDirection = event.get_scroll_direction()[1]; if (isScrollUp(event)) {
if (eventDirection === Gdk.ScrollDirection.UP) {
throttledScrollUp();
} else if (eventDirection === Gdk.ScrollDirection.DOWN) {
throttledScrollDown(); throttledScrollDown();
} }
});
self.disconnect(scrollHandlers); if (isScrollDown(event)) {
throttledScrollUp();
}
});
}); });
}, },
}, },

View File

@@ -154,6 +154,12 @@ export const CustomModuleSettings = (): JSX.Element => {
/> />
<Option opt={options.bar.customModules.netstat.icon} title="Netstat Icon" type="string" /> <Option opt={options.bar.customModules.netstat.icon} title="Netstat Icon" type="string" />
<Option opt={options.bar.customModules.netstat.label} title="Show Label" type="boolean" /> <Option opt={options.bar.customModules.netstat.label} title="Show Label" type="boolean" />
<Option opt={options.bar.customModules.netstat.networkInLabel} title="Network In Label" type="string" />
<Option
opt={options.bar.customModules.netstat.networkOutLabel}
title="Network Out Label"
type="string"
/>
<Option <Option
opt={options.bar.customModules.netstat.rateUnit} opt={options.bar.customModules.netstat.rateUnit}
title="Rate Unit" title="Rate Unit"
@@ -170,7 +176,7 @@ export const CustomModuleSettings = (): JSX.Element => {
<Option opt={options.bar.customModules.netstat.round} title="Round" type="boolean" /> <Option opt={options.bar.customModules.netstat.round} title="Round" type="boolean" />
<Option <Option
opt={options.bar.customModules.netstat.pollingInterval} opt={options.bar.customModules.netstat.pollingInterval}
title="Polling Interval" title="Polling Interval (ms)"
type="number" type="number"
min={100} min={100}
max={60 * 24 * 1000} max={60 * 24 * 1000}
@@ -221,6 +227,12 @@ export const CustomModuleSettings = (): JSX.Element => {
/> />
<Option opt={options.bar.customModules.updates.icon.updated} title="No Updates Icon" type="string" /> <Option opt={options.bar.customModules.updates.icon.updated} title="No Updates Icon" type="string" />
<Option opt={options.bar.customModules.updates.label} title="Show Label" type="boolean" /> <Option opt={options.bar.customModules.updates.label} title="Show Label" type="boolean" />
<Option
opt={options.bar.customModules.updates.autoHide}
title="Auto Hide"
subtitle="Hides module when no updates are available."
type="boolean"
/>
<Option opt={options.bar.customModules.updates.padZero} title="Pad with 0" type="boolean" /> <Option opt={options.bar.customModules.updates.padZero} title="Pad with 0" type="boolean" />
<Option opt={options.theme.bar.buttons.modules.updates.spacing} title="Spacing" type="string" /> <Option opt={options.theme.bar.buttons.modules.updates.spacing} title="Spacing" type="string" />
<Option <Option

View File

@@ -14,6 +14,7 @@ export const Module = ({
label, label,
tooltipText, tooltipText,
boxClass, boxClass,
isVis,
props = {}, props = {},
showLabelBinding = bind(undefinedVar), showLabelBinding = bind(undefinedVar),
showLabel, showLabel,
@@ -86,7 +87,7 @@ export const Module = ({
return { return {
component, component,
tooltip_text: tooltipText, tooltip_text: tooltipText,
isVisible: true, isVis: isVis,
boxClass, boxClass,
props, props,
}; };

View File

@@ -26,9 +26,9 @@ export const WidgetContainer = (child: BarBoxChild): JSX.Element => {
if (child.isBox) { if (child.isBox) {
return ( return (
<box className={buttonClassName} visible={computeVisible(child)}> <eventbox visible={computeVisible(child)} {...child.props}>
{child.component} <box className={buttonClassName}>{child.component}</box>
</box> </eventbox>
); );
} }

View File

@@ -16,7 +16,7 @@ const Header = (): JSX.Element => (
<box className={'menu-label-container volume selected'} halign={Gtk.Align.FILL}> <box className={'menu-label-container volume selected'} halign={Gtk.Align.FILL}>
<label className={'menu-label audio volume'} halign={Gtk.Align.START} hexpand label={'Volume'} /> <label className={'menu-label audio volume'} halign={Gtk.Align.START} hexpand label={'Volume'} />
<button <button
className={'menu-label slider-toggle'} className={'menu-icon-button menu-label slider-toggle volume'}
onClick={(_, event) => { onClick={(_, event) => {
if (!isPrimaryClick(event)) { if (!isPrimaryClick(event)) {
return; return;

View File

@@ -1,7 +1,7 @@
import { bind } from 'astal'; import { bind } from 'astal';
import { Gdk, Gtk } from 'astal/gtk3'; import { Gdk, Gtk } from 'astal/gtk3';
import AstalWp from 'gi://AstalWp?version=0.1'; import AstalWp from 'gi://AstalWp?version=0.1';
import { capitalizeFirstLetter } from 'src/lib/utils'; import { capitalizeFirstLetter, isScrollDown, isScrollUp } from 'src/lib/utils';
import options from 'src/options'; import options from 'src/options';
const { raiseMaximumVolume } = options.menus.volume; const { raiseMaximumVolume } = options.menus.volume;
@@ -34,15 +34,14 @@ export const Slider = ({ device, type }: SliderProps): JSX.Element => {
}} }}
setup={(self) => { setup={(self) => {
self.connect('scroll-event', (_, event: Gdk.Event) => { self.connect('scroll-event', (_, event: Gdk.Event) => {
const [directionSuccess, direction] = event.get_scroll_direction(); if (isScrollUp(event)) {
const [deltasSuccess, , yScroll] = event.get_scroll_deltas(); const newVolume = device.volume + 0.05;
device.set_volume(Math.min(newVolume, 1));
}
if (directionSuccess) { if (isScrollDown(event)) {
const newVolume = device.volume + (direction === Gdk.ScrollDirection.DOWN ? 0.05 : -0.05); const newVolume = device.volume - 0.05;
device.set_volume(Math.min(newVolume, 1)); device.set_volume(newVolume);
} else if (deltasSuccess) {
const newVolume = device.volume - yScroll / 100;
device.set_volume(Math.min(newVolume, 1));
} }
}); });
}} }}

View File

@@ -6,7 +6,7 @@ import { isDiscovering } from './helper';
export const DiscoverButton = (): JSX.Element => ( export const DiscoverButton = (): JSX.Element => (
<button <button
className="menu-icon-button search" className="menu-icon-button search bluetooth"
valign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}
onClick={(_, self) => { onClick={(_, self) => {
if (!isPrimaryClick(self)) { if (!isPrimaryClick(self)) {

View File

@@ -1,7 +1,7 @@
import { bind, exec } from 'astal'; import { bind, exec } from 'astal';
import GdkPixbuf from 'gi://GdkPixbuf';
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';
const { image, name } = options.menus.dashboard.powermenu.avatar; const { image, name } = options.menus.dashboard.powermenu.avatar;
@@ -11,12 +11,11 @@ const ProfilePicture = (): JSX.Element => {
className={'profile-picture'} className={'profile-picture'}
halign={Gtk.Align.CENTER} halign={Gtk.Align.CENTER}
css={bind(image).as((img) => { css={bind(image).as((img) => {
try { if (isAnImage(img)) {
GdkPixbuf.Pixbuf.new_from_file(img); return `background-image: url("${normalizePath(img)}")`;
return `background-image: url("${img}")`;
} catch {
return `background-image: url("${SRC_DIR}/assets/hyprpanel.png")`;
} }
return `background-image: url("${SRC_DIR}/assets/hyprpanel.png")`;
})} })}
/> />
); );

View File

@@ -24,36 +24,60 @@ const ShortcutButton = ({ shortcut, ...props }: ShortcutButtonProps): JSX.Elemen
}; };
export const LeftShortcut1 = (): JSX.Element => { export const LeftShortcut1 = (): JSX.Element => {
if (!hasCommand(left.shortcut1)) {
return <box />;
}
return ( return (
<ShortcutButton <ShortcutButton
shortcut={left.shortcut1} shortcut={left.shortcut1}
className={`dashboard-button top-button ${hasCommand(left.shortcut1) ? 'paired' : ''}`} className={`dashboard-button top-button ${hasCommand(left.shortcut2) ? 'paired' : ''}`}
/> />
); );
}; };
export const LeftShortcut2 = (): JSX.Element => { export const LeftShortcut2 = (): JSX.Element => {
if (!hasCommand(left.shortcut2)) {
return <box />;
}
return <ShortcutButton shortcut={left.shortcut2} className={`dashboard-button`} />; return <ShortcutButton shortcut={left.shortcut2} className={`dashboard-button`} />;
}; };
export const LeftShortcut3 = (): JSX.Element => { export const LeftShortcut3 = (): JSX.Element => {
if (!hasCommand(left.shortcut3)) {
return <box />;
}
return ( return (
<ShortcutButton <ShortcutButton
shortcut={left.shortcut3} shortcut={left.shortcut3}
className={`dashboard-button top-button ${hasCommand(left.shortcut3) ? 'paired' : ''}`} className={`dashboard-button top-button ${hasCommand(left.shortcut4) ? 'paired' : ''}`}
/> />
); );
}; };
export const LeftShortcut4 = (): JSX.Element => { export const LeftShortcut4 = (): JSX.Element => {
if (!hasCommand(left.shortcut4)) {
return <box />;
}
return <ShortcutButton shortcut={left.shortcut4} className={`dashboard-button `} />; return <ShortcutButton shortcut={left.shortcut4} className={`dashboard-button `} />;
}; };
export const RightShortcut1 = (): JSX.Element => { export const RightShortcut1 = (): JSX.Element => {
if (!hasCommand(right.shortcut1)) {
return <box />;
}
return <ShortcutButton shortcut={right.shortcut1} className={`dashboard-button top-button paired`} />; return <ShortcutButton shortcut={right.shortcut1} className={`dashboard-button top-button paired`} />;
}; };
export const RightShortcut3 = (): JSX.Element => { export const RightShortcut3 = (): JSX.Element => {
if (!hasCommand(right.shortcut3)) {
return <box />;
}
return <ShortcutButton shortcut={right.shortcut3} className={`dashboard-button top-button paired`} />; return <ShortcutButton shortcut={right.shortcut3} className={`dashboard-button top-button paired`} />;
}; };

View File

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

View File

@@ -57,7 +57,7 @@ export const LeftShortcuts = (): JSX.Element => {
return ( return (
<box className={'container most-used dashboard-card'}> <box className={'container most-used dashboard-card'}>
<LeftColumn visibleClass={isVisibleRight && isVisibleLeft}> <LeftColumn isVisible={isVisibleRight && isVisibleLeft}>
<LeftShortcut1 /> <LeftShortcut1 />
<LeftShortcut2 /> <LeftShortcut2 />
</LeftColumn> </LeftColumn>
@@ -78,7 +78,7 @@ export const RightShortcuts = (): JSX.Element => {
{Variable.derive(rightBindings, () => { {Variable.derive(rightBindings, () => {
return ( return (
<box className={`container utilities dashboard-card ${!leftCardHidden.get() ? 'paired' : ''}`}> <box className={`container utilities dashboard-card ${!leftCardHidden.get() ? 'paired' : ''}`}>
<LeftColumn visibleClass={!leftCardHidden.get()}> <LeftColumn isVisible={true}>
<RightShortcut1 /> <RightShortcut1 />
<SettingsButton /> <SettingsButton />
</LeftColumn> </LeftColumn>

View File

@@ -8,6 +8,14 @@ const { enable, duration, active_monitor, monitor } = options.theme.osd;
let count = 0; let count = 0;
/*
* So the OSD doesn't show on startup for no reason
*/
let isStartingUp = true;
timeout(3000, () => {
isStartingUp = false;
});
/** /**
* Handles the reveal state of a Widget.Revealer. * Handles the reveal state of a Widget.Revealer.
* *
@@ -69,6 +77,10 @@ export const handleRevealWindow = (self: Widget.Window, property: 'revealChild'
* @param property The property to check, either 'revealChild' or 'visible'. * @param property The property to check, either 'revealChild' or 'visible'.
*/ */
export const handleReveal = (self: Widget.Revealer | Widget.Window, property: 'revealChild' | 'visible'): void => { export const handleReveal = (self: Widget.Revealer | Widget.Window, property: 'revealChild' | 'visible'): void => {
if (isStartingUp) {
return;
}
if (self instanceof Widget.Revealer) { if (self instanceof Widget.Revealer) {
handleRevealRevealer(self, property); handleRevealRevealer(self, property);
} else if (self instanceof Widget.Window) { } else if (self instanceof Widget.Window) {

View File

@@ -29,19 +29,19 @@ export function warnOnLowBattery(): void {
// To avoid double notifications, we check each of the thresholds and set the correct `sentNotification`, but then // To avoid double notifications, we check each of the thresholds and set the correct `sentNotification`, but then
// combine them into one notification only // combine them into one notification only
let sendNotification = false; let sendNotification = false;
if (!sentLowNotification && batteryPercentage < lowThreshold) { if (!sentLowNotification && batteryPercentage <= lowThreshold) {
sentLowNotification = true; sentLowNotification = true;
sendNotification = true; sendNotification = true;
} }
if (!sentHalfLowNotification && batteryPercentage < lowThreshold / 2) { if (!sentHalfLowNotification && batteryPercentage <= lowThreshold / 2) {
sentHalfLowNotification = true; sentHalfLowNotification = true;
sendNotification = true; sendNotification = true;
} }
if (sendNotification) { if (sendNotification) {
Notify({ Notify({
summary: lowBatteryNotificationTitle.get().replace('$POWER_LEVEL', batteryPercentage.toString()), summary: lowBatteryNotificationTitle.get().replaceAll('$POWER_LEVEL', batteryPercentage.toString()),
body: lowBatteryNotificationText.get().replace('$POWER_LEVEL', batteryPercentage.toString()), body: lowBatteryNotificationText.get().replaceAll('$POWER_LEVEL', batteryPercentage.toString()),
iconName: icons.ui.warning, iconName: icons.ui.warning,
urgency: 'critical', urgency: 'critical',
}); });

View File

@@ -13,7 +13,7 @@ export type BarBoxChild = {
isBox?: boolean; isBox?: boolean;
boxClass: string; boxClass: string;
tooltip_text?: string | Binding<string>; tooltip_text?: string | Binding<string>;
} & ({ isBox: true; props: Widget.BoxProps } | { isBox?: false; props: Widget.ButtonProps }); } & ({ isBox: true; props: Widget.EventBoxProps } | { isBox?: false; props: Widget.ButtonProps });
export type SelfButton = Button<Child, Attribute>; export type SelfButton = Button<Child, Attribute>;
@@ -29,6 +29,7 @@ export type BarModule = {
boundLabel?: string; boundLabel?: string;
tooltipText?: string | Binding<string>; tooltipText?: string | Binding<string>;
boxClass: string; boxClass: string;
isVis?: Variable<boolean>;
props?: Widget.ButtonProps; props?: Widget.ButtonProps;
showLabel?: boolean; showLabel?: boolean;
showLabelBinding?: Binding; showLabelBinding?: Binding;

View File

@@ -11,7 +11,6 @@ import options from '../options';
import { Astal, Gdk, Gtk } from 'astal/gtk3'; import { Astal, Gdk, Gtk } from 'astal/gtk3';
import AstalApps from 'gi://AstalApps?version=0.1'; import AstalApps from 'gi://AstalApps?version=0.1';
import { exec, execAsync } from 'astal/process'; import { exec, execAsync } from 'astal/process';
import { Gio } from 'astal';
/** /**
* Handles errors by throwing a new Error with a message. * Handles errors by throwing a new Error with a message.
@@ -228,25 +227,40 @@ export function launchApp(app: AstalApps.Application): void {
* This function attempts to load an image from the specified filepath using GdkPixbuf. * This function attempts to load an image from the specified filepath using GdkPixbuf.
* If the image is successfully loaded, it returns true. Otherwise, it logs an error and returns false. * If the image is successfully loaded, it returns true. Otherwise, it logs an error and returns false.
* *
* Note: Unlike GdkPixbuf, this function will normalize the given path.
*
* @param imgFilePath The path to the image file. * @param imgFilePath The path to the image file.
* *
* @returns True if the filepath is a valid image, false otherwise. * @returns True if the filepath is a valid image, false otherwise.
*/ */
export function isAnImage(imgFilePath: string): boolean { export function isAnImage(imgFilePath: string): boolean {
try { try {
const file = Gio.File.new_for_path(imgFilePath); GdkPixbuf.Pixbuf.new_from_file(normalizePath(imgFilePath));
if (!file.query_exists(null)) {
return false;
}
GdkPixbuf.Pixbuf.new_from_file(imgFilePath);
return true; return true;
} catch (error) { } catch (error) {
console.error(error); console.info(error);
return false; return false;
} }
} }
/**
* Normalize a path to the absolute representation of the path.
*
* Note: This will only expand '~' if present. Path traversal is not supported.
*
* @param path The path to normalize.
*
* @returns The normalized path.
*/
export function normalizePath(path: string): string {
if (path.charAt(0) == '~') {
// Replace will only replace the first match, in this case, the first character
return path.replace('~', GLib.get_home_dir());
}
return path;
}
/** /**
* Sends a notification using the `notify-send` command. * Sends a notification using the `notify-send` command.
* *
@@ -399,7 +413,20 @@ export const isMiddleClick = (event: Astal.ClickEvent): boolean => event.button
* *
* @returns True if the event is a scroll up, false otherwise. * @returns True if the event is a scroll up, false otherwise.
*/ */
export const isScrollUp = (event: Astal.ScrollEvent): boolean => event.direction === Gdk.ScrollDirection.UP; export const isScrollUp = (event: Gdk.Event): boolean => {
const [directionSuccess, direction] = event.get_scroll_direction();
const [deltaSuccess, , yScroll] = event.get_scroll_deltas();
if (directionSuccess && direction === Gdk.ScrollDirection.UP) {
return true;
}
if (deltaSuccess && yScroll < 0) {
return true;
}
return false;
};
/** /**
* Checks if an event is a scroll down. * Checks if an event is a scroll down.
@@ -410,4 +437,17 @@ export const isScrollUp = (event: Astal.ScrollEvent): boolean => event.direction
* *
* @returns True if the event is a scroll down, false otherwise. * @returns True if the event is a scroll down, false otherwise.
*/ */
export const isScrollDown = (event: Astal.ScrollEvent): boolean => event.direction === Gdk.ScrollDirection.DOWN; export const isScrollDown = (event: Gdk.Event): boolean => {
const [directionSuccess, direction] = event.get_scroll_direction();
const [deltaSuccess, , yScroll] = event.get_scroll_deltas();
if (directionSuccess && direction === Gdk.ScrollDirection.DOWN) {
return true;
}
if (deltaSuccess && yScroll > 0) {
return true;
}
return false;
};

View File

@@ -1072,6 +1072,8 @@ const options = mkOptions(CONFIG, {
networkInterface: opt(''), networkInterface: opt(''),
dynamicIcon: opt(false), dynamicIcon: opt(false),
icon: opt('󰖟'), icon: opt('󰖟'),
networkInLabel: opt('↓'),
networkOutLabel: opt('↑'),
round: opt(true), round: opt(true),
labelType: opt<NetstatLabelType>('full'), labelType: opt<NetstatLabelType>('full'),
rateUnit: opt<RateUnit>('auto'), rateUnit: opt<RateUnit>('auto'),
@@ -1094,6 +1096,7 @@ const options = mkOptions(CONFIG, {
updateCommand: opt(`${SRC_DIR}/scripts/checkUpdates.sh -arch`), updateCommand: opt(`${SRC_DIR}/scripts/checkUpdates.sh -arch`),
label: opt(true), label: opt(true),
padZero: opt(true), padZero: opt(true),
autoHide: opt(false),
icon: { icon: {
pending: opt('󰏗'), pending: opt('󰏗'),
updated: opt('󰏖'), updated: opt('󰏖'),
@@ -1199,7 +1202,7 @@ const options = mkOptions(CONFIG, {
logout: opt('hyprctl dispatch exit'), logout: opt('hyprctl dispatch exit'),
shutdown: opt('systemctl poweroff'), shutdown: opt('systemctl poweroff'),
avatar: { avatar: {
image: opt('$HOME/.face.icon'), image: opt('~/.face.icon'),
name: opt<'system' | string>('system'), name: opt<'system' | string>('system'),
}, },
}, },

View File

@@ -1,5 +1,5 @@
import icons from '../lib/icons/icons'; import icons from '../lib/icons/icons';
import { bash, dependencies, Notify, isAnImage } from '../lib/utils'; import { bash, dependencies, Notify, isAnImage, normalizePath } from '../lib/utils';
import options from '../options'; import options from '../options';
import Wallpaper from 'src/services/Wallpaper'; import Wallpaper from 'src/services/Wallpaper';
@@ -8,7 +8,7 @@ const { matugen } = options.theme;
const ensureMatugenWallpaper = (): void => { const ensureMatugenWallpaper = (): void => {
const wallpaperPath = options.wallpaper.image.get(); const wallpaperPath = options.wallpaper.image.get();
if (matugen.get() && (!options.wallpaper.image.get().length || !isAnImage(wallpaperPath))) { if (matugen.get() && (!wallpaperPath.length || !isAnImage(normalizePath(wallpaperPath)))) {
Notify({ Notify({
summary: 'Matugen Failed', summary: 'Matugen Failed',
body: "Please select a wallpaper in 'Theming > General' first.", body: "Please select a wallpaper in 'Theming > General' first.",

View File

@@ -31,6 +31,18 @@
color: if($bar-menus-monochrome, $bar-menus-icons-active, $bar-menus-menu-volume-icons-active); color: if($bar-menus-monochrome, $bar-menus-icons-active, $bar-menus-menu-volume-icons-active);
} }
.menu-icon-button.volume {
color: if($bar-menus-monochrome, $bar-menus-iconbuttons-passive, $bar-menus-menu-volume-iconbutton-passive);
padding: 0em 1em;
label {
font-size: 1.25em;
}
&:hover {
color: if($bar-menus-monochrome, $bar-menus-iconbuttons-active, $bar-menus-menu-volume-iconbutton-active);
}
}
.menu-slider.playback { .menu-slider.playback {
trough { trough {
background: if( background: if(
@@ -213,18 +225,6 @@
border-radius: 0em; border-radius: 0em;
} }
.slider-toggle {
color: if($bar-menus-monochrome, $bar-menus-iconbuttons-passive, $bar-menus-menu-volume-iconbutton-passive);
padding: 0em 1em;
label {
font-size: 1.25em;
}
&:hover {
color: if($bar-menus-monochrome, $bar-menus-iconbuttons-active, $bar-menus-menu-volume-iconbutton-active);
}
}
.active-playbacks-scrollable { .active-playbacks-scrollable {
min-height: 12.5em; min-height: 12.5em;