Implemented strict linting standards and prettier formatting config. (#248)

* Implemented strict linting standards and prettier formatting config.

* More linter fixes and type updates.

* More linter updates and type fixes

* Remove noisy comments

* Linter and type updates

* Linter, formatting and type updates.

* Linter updates

* Type updates

* Type updates

* fixed all linter errors

* Fixed all linting, formatting and type issues.

* Resolve merge conflicts.
This commit is contained in:
Jas Singh
2024-09-14 16:20:05 -07:00
committed by GitHub
parent ff13e3dd3c
commit 2c72cc66d8
222 changed files with 13141 additions and 8433 deletions

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
types
node_modules

25
.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'import'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
ignorePatterns: ['.eslintrc.js', 'types/**/*.ts'],
env: {
es6: true,
browser: true,
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'import/extensions': ['off'],
'import/no-unresolved': 'off',
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
},
};

View File

@@ -1,130 +0,0 @@
env:
es2022: true
extends:
- "eslint:recommended"
- "plugin:@typescript-eslint/recommended"
parser: "@typescript-eslint/parser"
parserOptions:
ecmaVersion: 2022
sourceType: "module"
project: "./tsconfig.json"
warnOnUnsupportedTypeScriptVersion: false
root: true
ignorePatterns:
- types/
plugins:
- "@typescript-eslint"
rules:
"@typescript-eslint/ban-ts-comment":
- "off"
"@typescript-eslint/no-non-null-assertion":
- "off"
# "@typescript-eslint/no-explicit-any":
# - "off"
"@typescript-eslint/no-unused-vars":
- error
- varsIgnorePattern: (^unused|_$)
argsIgnorePattern: ^(unused|_)
"@typescript-eslint/no-empty-interface":
- "off"
arrow-parens:
- error
- as-needed
comma-dangle:
- error
- always-multiline
comma-spacing:
- error
- before: false
after: true
comma-style:
- error
- last
curly:
- error
- multi-or-nest
- consistent
dot-location:
- error
- property
eol-last:
- error
eqeqeq:
- error
- always
indent:
- error
- 4
- SwitchCase: 1
keyword-spacing:
- error
- before: true
lines-between-class-members:
- error
- always
- exceptAfterSingleLine: true
padded-blocks:
- error
- never
- allowSingleLineBlocks: false
prefer-const:
- error
quotes:
- error
- double
- avoidEscape: true
semi:
- error
- never
nonblock-statement-body-position:
- error
- below
no-trailing-spaces:
- error
no-useless-escape:
- off
max-len:
- error
- code: 100
func-call-spacing:
- error
array-bracket-spacing:
- error
space-before-function-paren:
- error
- anonymous: never
named: never
asyncArrow: ignore
space-before-blocks:
- error
key-spacing:
- error
object-curly-spacing:
- error
- always
globals:
Widget: readonly
Utils: readonly
App: readonly
Variable: readonly
Service: readonly
pkg: readonly
ARGV: readonly
Debugger: readonly
GIRepositoryGType: readonly
globalThis: readonly
imports: readonly
Intl: readonly
log: readonly
logError: readonly
print: readonly
printerr: readonly
window: readonly
TextEncoder: readonly
TextDecoder: readonly
console: readonly
setTimeout: readonly
setInterval: readonly
clearTimeout: readonly
clearInterval: readonly

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.weather.json .weather.json
node_modules

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
.eslintrc.js
types/**/*.ts

8
.prettierrc Normal file
View File

@@ -0,0 +1,8 @@
{
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"printWidth": 120,
"tabWidth": 4,
"useTabs": false
}

View File

@@ -1,46 +1,58 @@
import GLib from "gi://GLib" import GLib from 'gi://GLib';
const main = "/tmp/ags/hyprpanel/main.js" const main = '/tmp/ags/hyprpanel/main.js';
const entry = `${App.configDir}/main.ts` const entry = `${App.configDir}/main.ts`;
const bundler = GLib.getenv("AGS_BUNDLER") || "bun" const bundler = GLib.getenv('AGS_BUNDLER') || 'bun';
const v = { const v = {
ags: pkg.version?.split(".").map(Number) || [], ags: pkg.version?.split('.').map(Number) || [],
expect: [1, 8, 1], expect: [1, 8, 1],
} };
try { try {
switch (bundler) { switch (bundler) {
case "bun": await Utils.execAsync([ case 'bun':
"bun", "build", entry, await Utils.execAsync([
"--outfile", main, 'bun',
"--external", "resource://*", 'build',
"--external", "gi://*", entry,
"--external", "file://*", '--outfile',
]); break main,
'--external',
'resource://*',
'--external',
'gi://*',
'--external',
'file://*',
]);
break;
case "esbuild": await Utils.execAsync([ case 'esbuild':
"esbuild", "--bundle", entry, await Utils.execAsync([
"--format=esm", 'esbuild',
'--bundle',
entry,
'--format=esm',
`--outfile=${main}`, `--outfile=${main}`,
"--external:resource://*", '--external:resource://*',
"--external:gi://*", '--external:gi://*',
"--external:file://*", '--external:file://*',
]); break ]);
break;
default: default:
throw `"${bundler}" is not a valid bundler` throw `"${bundler}" is not a valid bundler`;
} }
if (v.ags[1] < v.expect[1] || v.ags[2] < v.expect[2]) { if (v.ags[1] < v.expect[1] || v.ags[2] < v.expect[2]) {
print(`HyprPanel needs atleast v${v.expect.join(".")} of AGS, yours is v${v.ags.join(".")}`) print(`HyprPanel needs atleast v${v.expect.join('.')} of AGS, yours is v${v.ags.join('.')}`);
App.quit() App.quit();
} }
await import(`file://${main}`) await import(`file://${main}`);
} catch (error) { } catch (error) {
console.error(error) console.error(error);
App.quit() App.quit();
} }
export { } export {};

View File

@@ -1,26 +1,25 @@
import GLib from "gi://GLib?version=2.0"; import GLib from 'gi://GLib?version=2.0';
import { Binding } from "types/service"; import { GenericFunction } from 'lib/types/customModules/generic';
import { Variable as VariableType } from "types/variable"; import { Bind } from 'lib/types/variable';
import { Variable as VariableType } from 'types/variable';
type GenericFunction = (...args: any[]) => any;
/** /**
* @param {VariableType<T>} targetVariable - The Variable to update with the function's result. * @param {VariableType<T>} targetVariable - The Variable to update with the function's result.
* @param {Array<VariableType<any>>} trackers - Array of trackers to watch. * @param {Array<Bind>} trackers - Array of trackers to watch.
* @param {Binding<any, any, unknown>} pollingInterval - The polling interval in milliseconds. * @param {Bind} pollingInterval - The polling interval in milliseconds.
* @param {GenericFunction} someFunc - The function to execute at each interval, which updates the Variable. * @param {GenericFunction<T, P>} someFunc - The function to execute at each interval, which updates the Variable.
* @param {...any} params - Parameters to pass to someFunc. * @param {...P} params - Parameters to pass to someFunc.
*/ */
export const pollVariable = <T>( export const pollVariable = <T, P extends unknown[], F extends GenericFunction<T, P>>(
targetVariable: VariableType<T>, targetVariable: VariableType<T>,
trackers: Array<Binding<any, any, unknown>>, trackers: Array<Bind>,
pollingInterval: Binding<any, any, unknown>, pollingInterval: Bind,
someFunc: GenericFunction, someFunc: F,
...params: any[] ...params: P
): void => { ): void => {
let intervalInstance: number | null = null; let intervalInstance: number | null = null;
const intervalFn = (pollIntrvl: number) => { const intervalFn = (pollIntrvl: number): void => {
if (intervalInstance !== null) { if (intervalInstance !== null) {
GLib.source_remove(intervalInstance); GLib.source_remove(intervalInstance);
} }
@@ -37,28 +36,31 @@ export const pollVariable = <T>(
/** /**
* @param {VariableType<T>} targetVariable - The Variable to update with the result of the command. * @param {VariableType<T>} targetVariable - The Variable to update with the result of the command.
* @param {Binding<any, any, unknown>} pollingInterval - The polling interval in milliseconds. * @param {Array<Bind>} trackers - Array of trackers to watch.
* @param {Bind} pollingInterval - The polling interval in milliseconds.
* @param {string} someCommand - The bash command to execute. * @param {string} someCommand - The bash command to execute.
* @param {GenericFunction} someFunc - The function to execute after processing the command result. * @param {GenericFunction<T, [unknown, ...P]>} someFunc - The function to execute after processing the command result;
* @param {...any} params - Parameters to pass to someFunc. * with the first argument being the result of the command execution.
* @param {...P} params - Additional parameters to pass to someFunc.
*/ */
export const pollVariableBash = <T>( export const pollVariableBash = <T, P extends unknown[], F extends GenericFunction<T, [string, ...P]>>(
targetVariable: VariableType<T>, targetVariable: VariableType<T>,
trackers: Array<Binding<any, any, unknown>>, trackers: Array<Bind>,
pollingInterval: Binding<any, any, unknown>, pollingInterval: Bind,
someCommand: string, someCommand: string,
someFunc: (res: any, ...params: any[]) => T, someFunc: F,
...params: any[] ...params: P
): void => { ): void => {
let intervalInstance: number | null = null; let intervalInstance: number | null = null;
const intervalFn = (pollIntrvl: number) => { const intervalFn = (pollIntrvl: number): void => {
if (intervalInstance !== null) { if (intervalInstance !== null) {
GLib.source_remove(intervalInstance); GLib.source_remove(intervalInstance);
} }
intervalInstance = Utils.interval(pollIntrvl, () => { intervalInstance = Utils.interval(pollIntrvl, () => {
Utils.execAsync(`bash -c "${someCommand}"`).then((res: any) => { Utils.execAsync(`bash -c "${someCommand}"`)
.then((res: string) => {
try { try {
targetVariable.value = someFunc(res, ...params); targetVariable.value = someFunc(res, ...params);
} catch (error) { } catch (error) {

View File

@@ -2,8 +2,10 @@ import { Option } from 'widget/settings/shared/Option';
import { Header } from 'widget/settings/shared/Header'; import { Header } from 'widget/settings/shared/Header';
import options from 'options'; import options from 'options';
import Scrollable from 'types/widgets/scrollable';
import { Attribute, GtkWidget } from 'lib/types/widget';
export const CustomModuleSettings = () => export const CustomModuleSettings = (): Scrollable<GtkWidget, Attribute> =>
Widget.Scrollable({ Widget.Scrollable({
vscroll: 'automatic', vscroll: 'automatic',
hscroll: 'automatic', hscroll: 'automatic',
@@ -204,17 +206,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.netstat.icon, opt: options.bar.customModules.netstat.icon,
title: 'Netstat Icon', title: 'Netstat Icon',
type: 'enum', type: 'enum',
enums: [ enums: ['󰖟', '󰇚', '󰕒', '󰛳', '', '󰣺', '󰖩', '', '󰈀'],
'󰖟',
'󰇚',
'󰕒',
'󰛳',
'',
'󰣺',
'󰖩',
'',
'󰈀',
],
}), }),
Option({ Option({
opt: options.bar.customModules.netstat.label, opt: options.bar.customModules.netstat.label,
@@ -336,17 +328,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.updates.icon, opt: options.bar.customModules.updates.icon,
title: 'Updates Icon', title: 'Updates Icon',
type: 'enum', type: 'enum',
enums: [ enums: ['󰚰', '󰇚', '', '󱑢', '󱑣', '󰏖', '', '󰏔', '󰏗'],
'󰚰',
'󰇚',
'',
'󱑢',
'󱑣',
'󰏖',
'',
'󰏔',
'󰏗',
],
}), }),
Option({ Option({
opt: options.bar.customModules.updates.label, opt: options.bar.customModules.updates.label,
@@ -367,7 +349,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.updates.pollingInterval, opt: options.bar.customModules.updates.pollingInterval,
title: 'Polling Interval', title: 'Polling Interval',
type: 'number', type: 'number',
subtitle: "WARNING: Be careful of your package manager\'s rate limit.", subtitle: "WARNING: Be careful of your package manager's rate limit.",
min: 100, min: 100,
max: 60 * 24 * 1000, max: 60 * 24 * 1000,
increment: 1000, increment: 1000,
@@ -491,4 +473,3 @@ export const CustomModuleSettings = () =>
], ],
}), }),
}); });

View File

@@ -1,10 +1,10 @@
// @ts-expect-error // @ts-expect-error: This import is a special directive that tells the compiler to use the GTop library
import GTop from 'gi://GTop'; import GTop from 'gi://GTop';
let previousCpuData = new GTop.glibtop_cpu(); let previousCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(previousCpuData); GTop.glibtop_get_cpu(previousCpuData);
export const computeCPU = () => { export const computeCPU = (): number => {
const currentCpuData = new GTop.glibtop_cpu(); const currentCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(currentCpuData); GTop.glibtop_get_cpu(currentCpuData);
@@ -17,5 +17,4 @@ export const computeCPU = () => {
previousCpuData = currentCpuData; previousCpuData = currentCpuData;
return cpuUsagePercentage; return cpuUsagePercentage;
} };

View File

@@ -1,28 +1,21 @@
import options from "options"; import options from 'options';
// Module initializer // Module initializer
import { module } from "../module" import { module } from '../module';
// import { CpuData } from "lib/types/customModules/cpu"; // import { CpuData } from "lib/types/customModules/cpu";
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
// Utility Methods // Utility Methods
import { inputHandler } from "customModules/utils"; import { inputHandler } from 'customModules/utils';
import { computeCPU } from "./computeCPU"; import { computeCPU } from './computeCPU';
import { pollVariable } from "customModules/PollVar"; import { pollVariable } from 'customModules/PollVar';
import { Module } from 'lib/types/bar';
// All the user configurable options for the cpu module that are needed // All the user configurable options for the cpu module that are needed
const { const { label, round, leftClick, rightClick, middleClick, scrollUp, scrollDown, pollingInterval } =
label, options.bar.customModules.cpu;
round,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
pollingInterval
} = options.bar.customModules.cpu;
export const cpuUsage = Variable(0); export const cpuUsage = Variable(0);
@@ -37,21 +30,19 @@ pollVariable(
computeCPU, computeCPU,
); );
export const Cpu = () => { export const Cpu = (): Module => {
const renderLabel = (cpuUsg: number, rnd: boolean) => { const renderLabel = (cpuUsg: number, rnd: boolean): string => {
return rnd ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`; return rnd ? `${Math.round(cpuUsg)}%` : `${cpuUsg.toFixed(2)}%`;
} };
const cpuModule = module({ const cpuModule = module({
textIcon: "", textIcon: '',
label: Utils.merge( label: Utils.merge([cpuUsage.bind('value'), round.bind('value')], (cpuUsg, rnd) => {
[cpuUsage.bind("value"), round.bind("value")],
(cpuUsg, rnd) => {
return renderLabel(cpuUsg, rnd); return renderLabel(cpuUsg, rnd);
}), }),
tooltipText: "CPU", tooltipText: 'CPU',
boxClass: "cpu", boxClass: 'cpu',
showLabelBinding: label.bind("value"), showLabelBinding: label.bind('value'),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -72,9 +63,8 @@ export const Cpu = () => {
}, },
}); });
}, },
} },
}); });
return cpuModule; return cpuModule;
} };

View File

@@ -1,12 +1,18 @@
import { HyprctlDeviceLayout, HyprctlKeyboard, KbLabelType, LayoutKeys, LayoutValues } from "lib/types/customModules/kbLayout"; import {
import { layoutMap } from "./layouts"; HyprctlDeviceLayout,
HyprctlKeyboard,
KbLabelType,
LayoutKeys,
LayoutValues,
} from 'lib/types/customModules/kbLayout';
import { layoutMap } from './layouts';
export const getKeyboardLayout = (obj: string, format: KbLabelType) => { export const getKeyboardLayout = (obj: string, format: KbLabelType): LayoutKeys | LayoutValues => {
let hyprctlDevices: HyprctlDeviceLayout = JSON.parse(obj); const hyprctlDevices: HyprctlDeviceLayout = JSON.parse(obj);
let keyboards = hyprctlDevices['keyboards']; const keyboards = hyprctlDevices['keyboards'];
if (keyboards.length === 0) { if (keyboards.length === 0) {
return "No KB!" return format === 'code' ? 'Unknown' : 'Unknown Layout';
} }
let mainKb = keyboards.find((kb: HyprctlKeyboard) => kb.main); let mainKb = keyboards.find((kb: HyprctlKeyboard) => kb.main);
@@ -15,8 +21,8 @@ export const getKeyboardLayout = (obj: string, format: KbLabelType) => {
mainKb = keyboards[keyboards.length - 1]; mainKb = keyboards[keyboards.length - 1];
} }
let layout: LayoutKeys = mainKb['active_keymap'] as LayoutKeys; const layout: LayoutKeys = mainKb['active_keymap'] as LayoutKeys;
const foundLayout: LayoutValues = layoutMap[layout]; const foundLayout: LayoutValues = layoutMap[layout];
return format === "code" ? foundLayout || layout : layout; return format === 'code' ? foundLayout || layout : layout;
} };

View File

@@ -1,49 +1,50 @@
const hyprland = await Service.import("hyprland"); const hyprland = await Service.import('hyprland');
import options from "options"; import options from 'options';
import { module } from "../module" import { module } from '../module';
import { inputHandler } from "customModules/utils"; import { inputHandler } from 'customModules/utils';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import Label from "types/widgets/label"; import Label from 'types/widgets/label';
import { getKeyboardLayout } from "./getLayout"; import { getKeyboardLayout } from './getLayout';
import { Module } from 'lib/types/bar';
const { const { label, labelType, icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } =
label, options.bar.customModules.kbLayout;
labelType,
icon,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.kbLayout;
export const KbInput = () => { export const KbInput = (): Module => {
const keyboardModule = module({ const keyboardModule = module({
textIcon: icon.bind("value"), textIcon: icon.bind('value'),
tooltipText: "", tooltipText: '',
labelHook: (self: Label<Gtk.Widget>): void => { labelHook: (self: Label<Gtk.Widget>): void => {
self.hook(hyprland, () => { self.hook(
hyprland,
() => {
Utils.execAsync('hyprctl devices -j') Utils.execAsync('hyprctl devices -j')
.then((obj) => { .then((obj) => {
self.label = getKeyboardLayout(obj, labelType.value); self.label = getKeyboardLayout(obj, labelType.value);
}) })
.catch((err) => { console.error(err); }); .catch((err) => {
}, "keyboard-layout"); console.error(err);
});
},
'keyboard-layout',
);
self.hook(labelType, () => { self.hook(labelType, () => {
Utils.execAsync('hyprctl devices -j') Utils.execAsync('hyprctl devices -j')
.then((obj) => { .then((obj) => {
self.label = getKeyboardLayout(obj, labelType.value); self.label = getKeyboardLayout(obj, labelType.value);
}) })
.catch((err) => { console.error(err); }); .catch((err) => {
console.error(err);
});
}); });
}, },
boxClass: "kblayout", boxClass: 'kblayout',
showLabelBinding: label.bind("value"), showLabelBinding: label.bind('value'),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -68,6 +69,4 @@ export const KbInput = () => {
}); });
return keyboardModule; return keyboardModule;
} };

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
import { Module } from "lib/types/bar"; import { BarBoxChild, Module } from 'lib/types/bar';
import { BarButtonStyles } from "lib/types/options"; import { BarButtonStyles } from 'lib/types/options';
import options from "options"; import { Bind } from 'lib/types/variable';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import { GtkWidget } from 'lib/types/widget';
import { Binding } from "types/service"; import options from 'options';
import { Variable as VariableType } from "types/variable"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
const { style } = options.theme.bar.buttons; const { style } = options.theme.bar.buttons;
@@ -16,45 +16,46 @@ export const module = ({
tooltipText, tooltipText,
boxClass, boxClass,
props = {}, props = {},
showLabelBinding = undefinedVar.bind("value"), showLabelBinding = undefinedVar.bind('value'),
showLabel, showLabel,
labelHook, labelHook,
hook hook,
}: Module) => { }: Module): BarBoxChild => {
const getIconWidget = () => { const getIconWidget = (): GtkWidget | undefined => {
let iconWidget: Gtk.Widget | undefined; let iconWidget: Gtk.Widget | undefined;
if (icon !== undefined) { if (icon !== undefined) {
iconWidget = Widget.Icon({ iconWidget = Widget.Icon({
class_name: `txt-icon bar-button-icon module-icon ${boxClass}`, class_name: `txt-icon bar-button-icon module-icon ${boxClass}`,
icon: icon icon: icon,
}) as unknown as Gtk.Widget; }) as unknown as Gtk.Widget;
} else if (textIcon !== undefined) { } else if (textIcon !== undefined) {
iconWidget = Widget.Label({ iconWidget = Widget.Label({
class_name: `txt-icon bar-button-icon module-icon ${boxClass}`, class_name: `txt-icon bar-button-icon module-icon ${boxClass}`,
label: textIcon label: textIcon,
}) as unknown as Gtk.Widget; }) as unknown as Gtk.Widget;
} }
return iconWidget; return iconWidget;
} };
return { return {
component: Widget.Box({ component: Widget.Box({
className: Utils.merge([style.bind("value"), showLabelBinding], (style: BarButtonStyles, shwLabel: boolean) => { className: Utils.merge(
[style.bind('value'), showLabelBinding],
(style: BarButtonStyles, shwLabel: boolean) => {
const shouldShowLabel = shwLabel || showLabel; const shouldShowLabel = shwLabel || showLabel;
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `${boxClass} ${styleMap[style]} ${!shouldShowLabel ? "no-label" : ""}`; return `${boxClass} ${styleMap[style]} ${!shouldShowLabel ? 'no-label' : ''}`;
}), },
),
tooltip_text: tooltipText, tooltip_text: tooltipText,
children: Utils.merge( children: Utils.merge([showLabelBinding], (showLabelBinding): Gtk.Widget[] => {
[showLabelBinding],
(showLabelBinding): Gtk.Widget[] => {
const childrenArray: Gtk.Widget[] = []; const childrenArray: Gtk.Widget[] = [];
const iconWidget = getIconWidget(); const iconWidget = getIconWidget();
@@ -68,17 +69,16 @@ export const module = ({
class_name: `bar-button-label module-label ${boxClass}`, class_name: `bar-button-label module-label ${boxClass}`,
label: label, label: label,
setup: labelHook, setup: labelHook,
}) as unknown as Gtk.Widget }) as unknown as Gtk.Widget,
); );
} }
return childrenArray; return childrenArray;
} }) as Bind,
) as Binding<VariableType<Gtk.Widget[]>, any, Gtk.Widget[]>,
setup: hook, setup: hook,
}), }),
tooltip_text: tooltipText, tooltip_text: tooltipText,
isVisible: true, isVisible: true,
boxClass, boxClass,
props props,
}; };
}; };

View File

@@ -71,7 +71,11 @@ const getNetworkUsage = (interfaceName: string = ''): NetworkUsage => {
return { name: '', rx: 0, tx: 0 }; return { name: '', rx: 0, tx: 0 };
}; };
export const computeNetwork = (round: VariableType<boolean>, interfaceNameVar: VariableType<string>, dataType: VariableType<RateUnit>): NetworkResourceData => { export const computeNetwork = (
round: VariableType<boolean>,
interfaceNameVar: VariableType<string>,
dataType: VariableType<RateUnit>,
): NetworkResourceData => {
const rateUnit = dataType.value; const rateUnit = dataType.value;
const interfaceName = interfaceNameVar ? interfaceNameVar.value : ''; const interfaceName = interfaceNameVar ? interfaceNameVar.value : '';

View File

@@ -2,7 +2,7 @@ import options from 'options';
import { module } from '../module'; import { module } from '../module';
import { inputHandler } from 'customModules/utils'; import { inputHandler } from 'customModules/utils';
import { computeNetwork } from './computeNetwork'; import { computeNetwork } from './computeNetwork';
import { NetstatLabelType } from 'lib/types/bar'; import { Module, NetstatLabelType } from 'lib/types/bar';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0'; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from 'types/widgets/button'; import Button from 'types/widgets/button';
import { NetworkResourceData } from 'lib/types/customModules/network'; import { NetworkResourceData } from 'lib/types/customModules/network';
@@ -23,9 +23,7 @@ const {
pollingInterval, pollingInterval,
} = options.bar.customModules.netstat; } = options.bar.customModules.netstat;
export const networkUsage = Variable<NetworkResourceData>( export const networkUsage = Variable<NetworkResourceData>(GET_DEFAULT_NETSTAT_DATA(rateUnit.value));
GET_DEFAULT_NETSTAT_DATA(rateUnit.value),
);
pollVariable( pollVariable(
// Variable to poll and update with the result of the function passed in // Variable to poll and update with the result of the function passed in
@@ -48,11 +46,8 @@ pollVariable(
rateUnit, rateUnit,
); );
export const Netstat = () => { export const Netstat = (): Module => {
const renderNetworkLabel = ( const renderNetworkLabel = (lblType: NetstatLabelType, network: NetworkResourceData): string => {
lblType: NetstatLabelType,
network: NetworkResourceData,
): string => {
switch (lblType) { switch (lblType) {
case 'in': case 'in':
return `${network.in}`; return `${network.in}`;
@@ -88,16 +83,14 @@ export const Netstat = () => {
}, },
onScrollUp: { onScrollUp: {
fn: () => { fn: () => {
labelType.value = labelType.value = NETWORK_LABEL_TYPES[
NETWORK_LABEL_TYPES[
(NETWORK_LABEL_TYPES.indexOf(labelType.value) + 1) % NETWORK_LABEL_TYPES.length (NETWORK_LABEL_TYPES.indexOf(labelType.value) + 1) % NETWORK_LABEL_TYPES.length
] as NetstatLabelType; ] as NetstatLabelType;
}, },
}, },
onScrollDown: { onScrollDown: {
fn: () => { fn: () => {
labelType.value = labelType.value = NETWORK_LABEL_TYPES[
NETWORK_LABEL_TYPES[
(NETWORK_LABEL_TYPES.indexOf(labelType.value) - 1 + NETWORK_LABEL_TYPES.length) % (NETWORK_LABEL_TYPES.indexOf(labelType.value) - 1 + NETWORK_LABEL_TYPES.length) %
NETWORK_LABEL_TYPES.length NETWORK_LABEL_TYPES.length
] as NetstatLabelType; ] as NetstatLabelType;
@@ -110,4 +103,3 @@ export const Netstat = () => {
return netstatModule; return netstatModule;
}; };

View File

@@ -1,24 +1,18 @@
import options from "options"; import options from 'options';
import { module } from "../module" import { module } from '../module';
import { inputHandler } from "customModules/utils"; import { inputHandler } from 'customModules/utils';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import { Module } from 'lib/types/bar';
const { const { icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } = options.bar.customModules.power;
icon,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.power;
export const Power = () => { export const Power = (): Module => {
const powerModule = module({ const powerModule = module({
tooltipText: "Power Menu", tooltipText: 'Power Menu',
textIcon: icon.bind("value"), textIcon: icon.bind('value'),
boxClass: "powermodule", boxClass: 'powermodule',
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -43,4 +37,4 @@ export const Power = () => {
}); });
return powerModule; return powerModule;
} };

View File

@@ -1,9 +1,10 @@
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
import { divide } from 'customModules/utils'; import { divide } from 'customModules/utils';
import { GenericResourceData } from 'lib/types/customModules/generic';
import { Variable as VariableType } from 'types/variable'; import { Variable as VariableType } from 'types/variable';
export const calculateRamUsage = (round: VariableType<boolean>) => { export const calculateRamUsage = (round: VariableType<boolean>): GenericResourceData => {
try { try {
const [success, meminfoBytes] = GLib.file_get_contents('/proc/meminfo'); const [success, meminfoBytes] = GLib.file_get_contents('/proc/meminfo');
@@ -26,17 +27,14 @@ export const calculateRamUsage = (round: VariableType<boolean>) => {
let usedRam = totalRamInBytes - availableRamInBytes; let usedRam = totalRamInBytes - availableRamInBytes;
usedRam = isNaN(usedRam) || usedRam < 0 ? 0 : usedRam; usedRam = isNaN(usedRam) || usedRam < 0 ? 0 : usedRam;
return { return {
percentage: divide([totalRamInBytes, usedRam], round.value), percentage: divide([totalRamInBytes, usedRam], round.value),
total: totalRamInBytes, total: totalRamInBytes,
used: usedRam, used: usedRam,
free: availableRamInBytes, free: availableRamInBytes,
}; };
} catch (error) { } catch (error) {
console.error('Error calculating RAM usage:', error); console.error('Error calculating RAM usage:', error);
return { total: 0, used: 0, percentage: 0 }; return { total: 0, used: 0, percentage: 0, free: 0 };
} }
}; };

View File

@@ -1,62 +1,48 @@
import options from "options"; import options from 'options';
// Module initializer // Module initializer
import { module } from "../module" import { module } from '../module';
// Types // Types
import { GenericResourceData } from "lib/types/customModules/generic"; import { GenericResourceData } from 'lib/types/customModules/generic';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
// Helper Methods // Helper Methods
import { calculateRamUsage } from "./computeRam"; import { calculateRamUsage } from './computeRam';
// Utility Methods // Utility Methods
import { formatTooltip, inputHandler, renderResourceLabel } from "customModules/utils"; import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils';
import { ResourceLabelType } from "lib/types/bar"; import { Module, ResourceLabelType } from 'lib/types/bar';
// Global Constants // Global Constants
import { LABEL_TYPES } from "lib/types/defaults/bar"; import { LABEL_TYPES } from 'lib/types/defaults/bar';
import { pollVariable } from "customModules/PollVar"; import { pollVariable } from 'customModules/PollVar';
// All the user configurable options for the ram module that are needed // All the user configurable options for the ram module that are needed
const { const { label, labelType, round, leftClick, rightClick, middleClick, pollingInterval } = options.bar.customModules.ram;
label,
labelType,
round,
leftClick,
rightClick,
middleClick,
pollingInterval
} = options.bar.customModules.ram;
const defaultRamData: GenericResourceData = { total: 0, used: 0, percentage: 0, free: 0 }; const defaultRamData: GenericResourceData = { total: 0, used: 0, percentage: 0, free: 0 };
const ramUsage = Variable(defaultRamData); const ramUsage = Variable<GenericResourceData>(defaultRamData);
pollVariable( pollVariable(ramUsage, [round.bind('value')], pollingInterval.bind('value'), calculateRamUsage, round);
ramUsage,
[round.bind('value')],
pollingInterval.bind('value'),
calculateRamUsage,
round,
);
export const Ram = () => {
export const Ram = (): Module => {
const ramModule = module({ const ramModule = module({
textIcon: "", textIcon: '',
label: Utils.merge( label: Utils.merge(
[ramUsage.bind("value"), labelType.bind("value"), round.bind("value")], [ramUsage.bind('value'), labelType.bind('value'), round.bind('value')],
(rmUsg: GenericResourceData, lblTyp: ResourceLabelType, round: boolean) => { (rmUsg: GenericResourceData, lblTyp: ResourceLabelType, round: boolean) => {
const returnValue = renderResourceLabel(lblTyp, rmUsg, round); const returnValue = renderResourceLabel(lblTyp, rmUsg, round);
return returnValue; return returnValue;
}), },
tooltipText: labelType.bind("value").as(lblTyp => { ),
tooltipText: labelType.bind('value').as((lblTyp) => {
return formatTooltip('RAM', lblTyp); return formatTooltip('RAM', lblTyp);
}), }),
boxClass: "ram", boxClass: 'ram',
showLabelBinding: label.bind("value"), showLabelBinding: label.bind('value'),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -71,18 +57,22 @@ export const Ram = () => {
}, },
onScrollUp: { onScrollUp: {
fn: () => { fn: () => {
labelType.value = LABEL_TYPES[(LABEL_TYPES.indexOf(labelType.value) + 1) % LABEL_TYPES.length] as ResourceLabelType; labelType.value = LABEL_TYPES[
} (LABEL_TYPES.indexOf(labelType.value) + 1) % LABEL_TYPES.length
] as ResourceLabelType;
},
}, },
onScrollDown: { onScrollDown: {
fn: () => { fn: () => {
labelType.value = LABEL_TYPES[(LABEL_TYPES.indexOf(labelType.value) - 1 + LABEL_TYPES.length) % LABEL_TYPES.length] as ResourceLabelType; labelType.value = LABEL_TYPES[
} (LABEL_TYPES.indexOf(labelType.value) - 1 + LABEL_TYPES.length) % LABEL_TYPES.length
] as ResourceLabelType;
},
}, },
}); });
}, },
} },
}); });
return ramModule; return ramModule;
} };

View File

@@ -1,23 +1,20 @@
// @ts-expect-error // @ts-expect-error is a special directive that tells the compiler to use the GTop library
import GTop from 'gi://GTop'; import GTop from 'gi://GTop';
import { divide } from 'customModules/utils'; import { divide } from 'customModules/utils';
import { Variable as VariableType } from 'types/variable'; import { Variable as VariableType } from 'types/variable';
import { GenericResourceData } from 'lib/types/customModules/generic';
let previousFsUsage = new GTop.glibtop_fsusage(); export const computeStorage = (round: VariableType<boolean>): GenericResourceData => {
export const computeStorage = (round: VariableType<boolean>) => {
try { try {
const currentFsUsage = new GTop.glibtop_fsusage(); const currentFsUsage = new GTop.glibtop_fsusage();
GTop.glibtop_get_fsusage(currentFsUsage, "/"); GTop.glibtop_get_fsusage(currentFsUsage, '/');
const total = currentFsUsage.blocks * currentFsUsage.block_size; const total = currentFsUsage.blocks * currentFsUsage.block_size;
const available = currentFsUsage.bavail * currentFsUsage.block_size; const available = currentFsUsage.bavail * currentFsUsage.block_size;
const used = total - available; const used = total - available;
previousFsUsage = currentFsUsage;
return { return {
total, total,
used, used,
@@ -26,7 +23,6 @@ export const computeStorage = (round: VariableType<boolean>) => {
}; };
} catch (error) { } catch (error) {
console.error('Error calculating RAM usage:', error); console.error('Error calculating RAM usage:', error);
return { total: 0, used: 0, percentage: 0 }; return { total: 0, used: 0, percentage: 0, free: 0 };
} }
}; };

View File

@@ -1,52 +1,38 @@
import options from "options"; import options from 'options';
import { module } from "../module" import { module } from '../module';
import { formatTooltip, inputHandler, renderResourceLabel } from "customModules/utils"; import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils';
import { computeStorage } from "./computeStorage"; import { computeStorage } from './computeStorage';
import { ResourceLabelType } from "lib/types/bar"; import { Module, ResourceLabelType } from 'lib/types/bar';
import { GenericResourceData } from "lib/types/customModules/generic"; import { GenericResourceData } from 'lib/types/customModules/generic';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import { LABEL_TYPES } from "lib/types/defaults/bar"; import { LABEL_TYPES } from 'lib/types/defaults/bar';
import { pollVariable } from "customModules/PollVar"; import { pollVariable } from 'customModules/PollVar';
const { const { label, labelType, icon, round, leftClick, rightClick, middleClick, pollingInterval } =
label, options.bar.customModules.storage;
labelType,
icon,
round,
leftClick,
rightClick,
middleClick,
pollingInterval
} = options.bar.customModules.storage;
const defaultStorageData = { total: 0, used: 0, percentage: 0, free: 0 }; const defaultStorageData = { total: 0, used: 0, percentage: 0, free: 0 };
const storageUsage = Variable(defaultStorageData); const storageUsage = Variable<GenericResourceData>(defaultStorageData);
pollVariable( pollVariable(storageUsage, [round.bind('value')], pollingInterval.bind('value'), computeStorage, round);
storageUsage,
[round.bind('value')],
pollingInterval.bind('value'),
computeStorage,
round,
);
export const Storage = () => { export const Storage = (): Module => {
const storageModule = module({ const storageModule = module({
textIcon: icon.bind("value"), textIcon: icon.bind('value'),
label: Utils.merge( label: Utils.merge(
[storageUsage.bind("value"), labelType.bind("value"), round.bind("value")], [storageUsage.bind('value'), labelType.bind('value'), round.bind('value')],
(storage: GenericResourceData, lblTyp: ResourceLabelType, round: boolean) => { (storage: GenericResourceData, lblTyp: ResourceLabelType, round: boolean) => {
return renderResourceLabel(lblTyp, storage, round); return renderResourceLabel(lblTyp, storage, round);
}), },
tooltipText: labelType.bind("value").as(lblTyp => { ),
tooltipText: labelType.bind('value').as((lblTyp) => {
return formatTooltip('Storage', lblTyp); return formatTooltip('Storage', lblTyp);
}), }),
boxClass: "storage", boxClass: 'storage',
showLabelBinding: label.bind("value"), showLabelBinding: label.bind('value'),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -61,18 +47,22 @@ export const Storage = () => {
}, },
onScrollUp: { onScrollUp: {
fn: () => { fn: () => {
labelType.value = LABEL_TYPES[(LABEL_TYPES.indexOf(labelType.value) + 1) % LABEL_TYPES.length] as ResourceLabelType; labelType.value = LABEL_TYPES[
} (LABEL_TYPES.indexOf(labelType.value) + 1) % LABEL_TYPES.length
] as ResourceLabelType;
},
}, },
onScrollDown: { onScrollDown: {
fn: () => { fn: () => {
labelType.value = LABEL_TYPES[(LABEL_TYPES.indexOf(labelType.value) - 1 + LABEL_TYPES.length) % LABEL_TYPES.length] as ResourceLabelType; labelType.value = LABEL_TYPES[
} (LABEL_TYPES.indexOf(labelType.value) - 1 + LABEL_TYPES.length) % LABEL_TYPES.length
] as ResourceLabelType;
},
}, },
}); });
}, },
} },
}); });
return storageModule; return storageModule;
} };

View File

@@ -1,106 +1,146 @@
import { Option } from "widget/settings/shared/Option"; import { Option } from 'widget/settings/shared/Option';
import { Header } from "widget/settings/shared/Header"; import { Header } from 'widget/settings/shared/Header';
import options from "options"; import options from 'options';
import Scrollable from 'types/widgets/scrollable';
import { Attribute, GtkWidget } from 'lib/types/widget';
export const CustomModuleTheme = () => { export const CustomModuleTheme = (): Scrollable<GtkWidget, Attribute> => {
return Widget.Scrollable({ return Widget.Scrollable({
vscroll: "automatic", vscroll: 'automatic',
hscroll: "automatic", hscroll: 'automatic',
class_name: "menu-theme-page customModules paged-container", class_name: 'menu-theme-page customModules paged-container',
child: Widget.Box({ child: Widget.Box({
class_name: "bar-theme-page paged-container", class_name: 'bar-theme-page paged-container',
vertical: true, vertical: true,
children: [ children: [
Header('RAM'), Header('RAM'),
Option({ opt: options.theme.bar.buttons.modules.ram.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.ram.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.ram.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.ram.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.ram.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.ram.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.ram.icon_background, opt: options.theme.bar.buttons.modules.ram.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('CPU'), Header('CPU'),
Option({ opt: options.theme.bar.buttons.modules.cpu.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.cpu.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.cpu.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.cpu.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.cpu.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.cpu.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.cpu.icon_background, opt: options.theme.bar.buttons.modules.cpu.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('Storage'), Header('Storage'),
Option({ opt: options.theme.bar.buttons.modules.storage.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.storage.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.storage.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.storage.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.storage.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.storage.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.storage.icon_background, opt: options.theme.bar.buttons.modules.storage.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('Netstat'), Header('Netstat'),
Option({ opt: options.theme.bar.buttons.modules.netstat.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.netstat.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.netstat.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.netstat.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.netstat.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.netstat.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.netstat.icon_background, opt: options.theme.bar.buttons.modules.netstat.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('Keyboard Layout'), Header('Keyboard Layout'),
Option({ opt: options.theme.bar.buttons.modules.kbLayout.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.kbLayout.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.kbLayout.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.kbLayout.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.kbLayout.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.kbLayout.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.kbLayout.icon_background, opt: options.theme.bar.buttons.modules.kbLayout.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('Updates'), Header('Updates'),
Option({ opt: options.theme.bar.buttons.modules.updates.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.updates.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.updates.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.updates.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.updates.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.updates.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.updates.icon_background, opt: options.theme.bar.buttons.modules.updates.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('Weather'), Header('Weather'),
Option({ opt: options.theme.bar.buttons.modules.weather.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.weather.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.weather.text, title: 'Text', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.weather.text, title: 'Text', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.weather.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.weather.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.weather.icon_background, opt: options.theme.bar.buttons.modules.weather.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
Header('Power'), Header('Power'),
Option({ opt: options.theme.bar.buttons.modules.power.icon, title: 'Icon', type: 'color' }), Option({ opt: options.theme.bar.buttons.modules.power.icon, title: 'Icon', type: 'color' }),
Option({ opt: options.theme.bar.buttons.modules.power.background, title: 'Label Background', type: 'color' }), Option({
opt: options.theme.bar.buttons.modules.power.background,
title: 'Label Background',
type: 'color',
}),
Option({ Option({
opt: options.theme.bar.buttons.modules.power.icon_background, opt: options.theme.bar.buttons.modules.power.icon_background,
title: 'Icon Background', title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.', subtitle:
type: 'color' "Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}), }),
] ],
}) }),
}) });
} };

View File

@@ -1,11 +1,12 @@
import options from "options"; import options from 'options';
import { module } from "../module" import { module } from '../module';
import { inputHandler } from "customModules/utils"; import { inputHandler } from 'customModules/utils';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import { Variable as VariableType } from "types/variable"; import { Variable as VariableType } from 'types/variable';
import { pollVariableBash } from "customModules/PollVar"; import { pollVariableBash } from 'customModules/PollVar';
import { Module } from 'lib/types/bar';
const { const {
updateCommand, updateCommand,
@@ -20,12 +21,12 @@ const {
scrollDown, scrollDown,
} = options.bar.customModules.updates; } = options.bar.customModules.updates;
const pendingUpdates: VariableType<string> = Variable(" 0"); const pendingUpdates: VariableType<string> = Variable(' 0');
const processUpdateCount = (updateCount: string) => { const processUpdateCount = (updateCount: string): string => {
if (!padZero.value) return updateCount; if (!padZero.value) return updateCount;
return `${updateCount.padStart(2, '0')}`; return `${updateCount.padStart(2, '0')}`;
} };
pollVariableBash( pollVariableBash(
pendingUpdates, pendingUpdates,
@@ -35,13 +36,13 @@ pollVariableBash(
processUpdateCount, processUpdateCount,
); );
export const Updates = () => { export const Updates = (): Module => {
const updatesModule = module({ const updatesModule = module({
textIcon: icon.bind("value"), textIcon: icon.bind('value'),
tooltipText: pendingUpdates.bind("value").as(v => `${v} updates available`), tooltipText: pendingUpdates.bind('value').as((v) => `${v} updates available`),
boxClass: "updates", boxClass: 'updates',
label: pendingUpdates.bind("value"), label: pendingUpdates.bind('value'),
showLabelBinding: label.bind("value"), showLabelBinding: label.bind('value'),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -66,7 +67,4 @@ export const Updates = () => {
}); });
return updatesModule; return updatesModule;
} };

View File

@@ -1,6 +1,8 @@
import { ResourceLabelType } from 'lib/types/bar'; import { ResourceLabelType } from 'lib/types/bar';
import { GenericResourceData } from 'lib/types/customModules/generic'; import { GenericResourceData, Postfix } from 'lib/types/customModules/generic';
import { InputHandlerEvents } from 'lib/types/customModules/utils'; import { InputHandlerEvents } from 'lib/types/customModules/utils';
import { ThrottleFn, ThrottleFnCallback } from 'lib/types/utils';
import { GtkWidget } from 'lib/types/widget';
import { Binding } from 'lib/utils'; import { Binding } from 'lib/utils';
import { openMenu } from 'modules/bar/utils'; import { openMenu } from 'modules/bar/utils';
import options from 'options'; import options from 'options';
@@ -13,14 +15,11 @@ const { scrollSpeed } = options.bar.customModules;
export const runAsyncCommand = ( export const runAsyncCommand = (
cmd: string, cmd: string,
fn: Function, fn: (output: string) => void,
events: { clicked: any; event: Gdk.Event } events: { clicked: Button<GtkWidget, GtkWidget>; event: Gdk.Event },
): void => { ): void => {
if (cmd.startsWith('menu:')) { if (cmd.startsWith('menu:')) {
// if the command starts with 'menu:', then it is a menu command
// and we should App.toggleMenu("menuName") based on the input menu:menuName. Ignoring spaces and case
const menuName = cmd.split(':')[1].trim().toLowerCase(); const menuName = cmd.split(':')[1].trim().toLowerCase();
openMenu(events.clicked, events.event, `${menuName}menu`); openMenu(events.clicked, events.event, `${menuName}menu`);
return; return;
@@ -32,15 +31,10 @@ export const runAsyncCommand = (
fn(output); fn(output);
} }
}) })
.catch((err) => .catch((err) => console.error(`Error running command "${cmd}": ${err})`));
console.error(`Error running command "${cmd}": ${err})`)
);
}; };
export function throttle<T extends (...args: any[]) => void>( export function throttle<T extends ThrottleFn>(func: T, limit: number): T {
func: T,
limit: number
): T {
let inThrottle: boolean; let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) { return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) { if (!inThrottle) {
@@ -53,31 +47,23 @@ export function throttle<T extends (...args: any[]) => void>(
} as T; } as T;
} }
export const throttledScrollHandler = (interval: number) => export const throttledScrollHandler = (interval: number): ThrottleFn =>
throttle((cmd: string, fn: Function | undefined) => { throttle((cmd: string, fn: ThrottleFnCallback) => {
Utils.execAsync(`bash -c "${cmd}"`) Utils.execAsync(`bash -c "${cmd}"`)
.then((output) => { .then((output) => {
if (fn !== undefined) { if (fn !== undefined) {
fn(output); fn(output);
} }
}) })
.catch((err) => .catch((err) => console.error(`Error running command "${cmd}": ${err}`));
console.error(`Error running command "${cmd}": ${err}`)
);
}, 200 / interval); }, 200 / interval);
const dummyVar = Variable(''); const dummyVar = Variable('');
export const inputHandler = ( export const inputHandler = (
self: Button<Gtk.Widget, Gtk.Widget>, self: Button<Gtk.Widget, Gtk.Widget>,
{ { onPrimaryClick, onSecondaryClick, onMiddleClick, onScrollUp, onScrollDown }: InputHandlerEvents,
onPrimaryClick, ): void => {
onSecondaryClick,
onMiddleClick,
onScrollUp,
onScrollDown,
}: InputHandlerEvents
) => {
const sanitizeInput = (input: VariableType<string>): string => { const sanitizeInput = (input: VariableType<string>): string => {
if (input === undefined) { if (input === undefined) {
return ''; return '';
@@ -89,46 +75,25 @@ export const inputHandler = (
const interval = scrollSpeed.value; const interval = scrollSpeed.value;
const throttledHandler = throttledScrollHandler(interval); const throttledHandler = throttledScrollHandler(interval);
self.on_primary_click = (clicked: any, event: Gdk.Event) => self.on_primary_click = (clicked: Button<GtkWidget, GtkWidget>, event: Gdk.Event): void =>
runAsyncCommand( runAsyncCommand(sanitizeInput(onPrimaryClick?.cmd || dummyVar), onPrimaryClick.fn, { clicked, event });
sanitizeInput(onPrimaryClick?.cmd || dummyVar),
onPrimaryClick.fn,
{ clicked, event }
);
self.on_secondary_click = (clicked: any, event: Gdk.Event) => self.on_secondary_click = (clicked: Button<GtkWidget, GtkWidget>, event: Gdk.Event): void =>
runAsyncCommand( runAsyncCommand(sanitizeInput(onSecondaryClick?.cmd || dummyVar), onSecondaryClick.fn, { clicked, event });
sanitizeInput(onSecondaryClick?.cmd || dummyVar),
onSecondaryClick.fn,
{ clicked, event }
);
self.on_middle_click = (clicked: any, event: Gdk.Event) => self.on_middle_click = (clicked: Button<GtkWidget, GtkWidget>, event: Gdk.Event): void =>
runAsyncCommand( runAsyncCommand(sanitizeInput(onMiddleClick?.cmd || dummyVar), onMiddleClick.fn, { clicked, event });
sanitizeInput(onMiddleClick?.cmd || dummyVar),
onMiddleClick.fn,
{ clicked, event }
);
self.on_scroll_up = () => self.on_scroll_up = (): void => throttledHandler(sanitizeInput(onScrollUp?.cmd || dummyVar), onScrollUp.fn);
throttledHandler(
sanitizeInput(onScrollUp?.cmd || dummyVar),
onScrollUp.fn
);
self.on_scroll_down = () => self.on_scroll_down = (): void =>
throttledHandler( throttledHandler(sanitizeInput(onScrollDown?.cmd || dummyVar), onScrollDown.fn);
sanitizeInput(onScrollDown?.cmd || dummyVar),
onScrollDown.fn
);
}; };
// Initial setup of event handlers // Initial setup of event handlers
updateHandlers(); updateHandlers();
const sanitizeVariable = ( const sanitizeVariable = (someVar: VariableType<string> | undefined): Binding<string> => {
someVar: VariableType<string> | undefined
): Binding<string> => {
if (someVar === undefined || typeof someVar.bind !== 'function') { if (someVar === undefined || typeof someVar.bind !== 'function') {
return dummyVar.bind('value'); return dummyVar.bind('value');
} }
@@ -145,37 +110,36 @@ export const inputHandler = (
sanitizeVariable(onScrollUp), sanitizeVariable(onScrollUp),
sanitizeVariable(onScrollDown), sanitizeVariable(onScrollDown),
], ],
updateHandlers updateHandlers,
); );
}; };
export const divide = ([total, used]: number[], round: boolean) => { export const divide = ([total, used]: number[], round: boolean): number => {
const percentageTotal = (used / total) * 100; const percentageTotal = (used / total) * 100;
if (round) { if (round) {
return total > 0 ? Math.round(percentageTotal) : 0; return total > 0 ? Math.round(percentageTotal) : 0;
} }
return total > 0 ? parseFloat(percentageTotal.toFixed(2)) : 0; return total > 0 ? parseFloat(percentageTotal.toFixed(2)) : 0;
}; };
export const formatSizeInKiB = (sizeInBytes: number, round: boolean) => { export const formatSizeInKiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / (1024 ** 1); const sizeInGiB = sizeInBytes / 1024 ** 1;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
}; };
export const formatSizeInMiB = (sizeInBytes: number, round: boolean) => { export const formatSizeInMiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / (1024 ** 2); const sizeInGiB = sizeInBytes / 1024 ** 2;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
}; };
export const formatSizeInGiB = (sizeInBytes: number, round: boolean) => { export const formatSizeInGiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / (1024 ** 3); const sizeInGiB = sizeInBytes / 1024 ** 3;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
}; };
export const formatSizeInTiB = (sizeInBytes: number, round: boolean) => { export const formatSizeInTiB = (sizeInBytes: number, round: boolean): number => {
const sizeInGiB = sizeInBytes / (1024 ** 4); const sizeInGiB = sizeInBytes / 1024 ** 4;
return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2)); return round ? Math.round(sizeInGiB) : parseFloat(sizeInGiB.toFixed(2));
}; };
export const autoFormatSize = (sizeInBytes: number, round: boolean) => { export const autoFormatSize = (sizeInBytes: number, round: boolean): number => {
// auto convert to GiB, MiB, KiB, TiB, or bytes // auto convert to GiB, MiB, KiB, TiB, or bytes
if (sizeInBytes >= 1024 ** 4) return formatSizeInTiB(sizeInBytes, round); if (sizeInBytes >= 1024 ** 4) return formatSizeInTiB(sizeInBytes, round);
if (sizeInBytes >= 1024 ** 3) return formatSizeInGiB(sizeInBytes, round); if (sizeInBytes >= 1024 ** 3) return formatSizeInGiB(sizeInBytes, round);
@@ -183,22 +147,18 @@ export const autoFormatSize = (sizeInBytes: number, round: boolean) => {
if (sizeInBytes >= 1024 ** 1) return formatSizeInKiB(sizeInBytes, round); if (sizeInBytes >= 1024 ** 1) return formatSizeInKiB(sizeInBytes, round);
return sizeInBytes; return sizeInBytes;
} };
export const getPostfix = (sizeInBytes: number) => { export const getPostfix = (sizeInBytes: number): Postfix => {
if (sizeInBytes >= 1024 ** 4) return 'TiB'; if (sizeInBytes >= 1024 ** 4) return 'TiB';
if (sizeInBytes >= 1024 ** 3) return 'GiB'; if (sizeInBytes >= 1024 ** 3) return 'GiB';
if (sizeInBytes >= 1024 ** 2) return 'MiB'; if (sizeInBytes >= 1024 ** 2) return 'MiB';
if (sizeInBytes >= 1024 ** 1) return 'KiB'; if (sizeInBytes >= 1024 ** 1) return 'KiB';
return 'B'; return 'B';
} };
export const renderResourceLabel = ( export const renderResourceLabel = (lblType: ResourceLabelType, rmUsg: GenericResourceData, round: boolean): string => {
lblType: ResourceLabelType,
rmUsg: GenericResourceData,
round: boolean
) => {
const { used, total, percentage, free } = rmUsg; const { used, total, percentage, free } = rmUsg;
const formatFunctions = { const formatFunctions = {
@@ -206,7 +166,7 @@ export const renderResourceLabel = (
GiB: formatSizeInGiB, GiB: formatSizeInGiB,
MiB: formatSizeInMiB, MiB: formatSizeInMiB,
KiB: formatSizeInKiB, KiB: formatSizeInKiB,
B: (size: number, _: boolean) => size B: (size: number): number => size,
}; };
// Get them datas in proper GiB, MiB, KiB, TiB, or bytes // Get them datas in proper GiB, MiB, KiB, TiB, or bytes
@@ -218,20 +178,20 @@ export const renderResourceLabel = (
const formatUsed = formatFunctions[postfix] || formatFunctions['B']; const formatUsed = formatFunctions[postfix] || formatFunctions['B'];
const usedSizeFormatted = formatUsed(used, round); const usedSizeFormatted = formatUsed(used, round);
if (lblType === "used/total") { if (lblType === 'used/total') {
return `${usedSizeFormatted}/${totalSizeFormatted} ${postfix}`; return `${usedSizeFormatted}/${totalSizeFormatted} ${postfix}`;
} }
if (lblType === "used") { if (lblType === 'used') {
return `${autoFormatSize(used, round)} ${getPostfix(used)}`; return `${autoFormatSize(used, round)} ${getPostfix(used)}`;
} }
if (lblType === "free") { if (lblType === 'free') {
return `${autoFormatSize(free, round)} ${getPostfix(free)}`; return `${autoFormatSize(free, round)} ${getPostfix(free)}`;
} }
return `${percentage}%`; return `${percentage}%`;
}; };
export const formatTooltip = (dataType: string, lblTyp: ResourceLabelType) => { export const formatTooltip = (dataType: string, lblTyp: ResourceLabelType): string => {
switch (lblTyp) { switch (lblTyp) {
case 'used': case 'used':
return `Used ${dataType}`; return `Used ${dataType}`;
@@ -244,4 +204,4 @@ export const formatTooltip = (dataType: string, lblTyp: ResourceLabelType) => {
default: default:
return ''; return '';
} }
} };

View File

@@ -1,40 +1,30 @@
import options from "options"; import options from 'options';
import { module } from "../module" import { module } from '../module';
import { inputHandler } from "customModules/utils"; import { inputHandler } from 'customModules/utils';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import { getWeatherStatusTextIcon, globalWeatherVar } from "globals/weather"; import { getWeatherStatusTextIcon, globalWeatherVar } from 'globals/weather';
import { Module } from 'lib/types/bar';
const { const { label, unit, leftClick, rightClick, middleClick, scrollUp, scrollDown } = options.bar.customModules.weather;
label,
unit,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.weather;
export const Weather = () => { export const Weather = (): Module => {
const weatherModule = module({ const weatherModule = module({
textIcon: Utils.merge([globalWeatherVar.bind("value")], (wthr) => { textIcon: Utils.merge([globalWeatherVar.bind('value')], (wthr) => {
const weatherStatusIcon = getWeatherStatusTextIcon(wthr); const weatherStatusIcon = getWeatherStatusTextIcon(wthr);
return weatherStatusIcon; return weatherStatusIcon;
}), }),
tooltipText: globalWeatherVar.bind("value").as(v => `Weather Status: ${v.current.condition.text}`), tooltipText: globalWeatherVar.bind('value').as((v) => `Weather Status: ${v.current.condition.text}`),
boxClass: "weather-custom", boxClass: 'weather-custom',
label: Utils.merge( label: Utils.merge([globalWeatherVar.bind('value'), unit.bind('value')], (wthr, unt) => {
[globalWeatherVar.bind("value"), unit.bind("value")], if (unt === 'imperial') {
(wthr, unt) => {
if (unt === "imperial") {
return `${Math.ceil(wthr.current.temp_f)}° F`; return `${Math.ceil(wthr.current.temp_f)}° F`;
} else { } else {
return `${Math.ceil(wthr.current.temp_c)}° C`; return `${Math.ceil(wthr.current.temp_c)}° C`;
} }
}, }),
), showLabelBinding: label.bind('value'),
showLabelBinding: label.bind("value"),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -59,8 +49,4 @@ export const Weather = () => {
}); });
return weatherModule; return weatherModule;
} };

View File

@@ -1,8 +1,8 @@
import Service from "resource:///com/github/Aylur/ags/service.js"; import Service from 'resource:///com/github/Aylur/ags/service.js';
import App from "resource:///com/github/Aylur/ags/app.js"; import App from 'resource:///com/github/Aylur/ags/app.js';
import { monitorFile } from "resource:///com/github/Aylur/ags/utils.js"; import { monitorFile } from 'resource:///com/github/Aylur/ags/utils.js';
import Gio from "gi://Gio"; import Gio from 'gi://Gio';
import { FileInfo } from "types/@girs/gio-2.0/gio-2.0.cjs"; import { FileInfo } from 'types/@girs/gio-2.0/gio-2.0.cjs';
class DirectoryMonitorService extends Service { class DirectoryMonitorService extends Service {
static { static {
@@ -14,23 +14,19 @@ class DirectoryMonitorService extends Service {
this.recursiveDirectoryMonitor(`${App.configDir}/scss`); this.recursiveDirectoryMonitor(`${App.configDir}/scss`);
} }
recursiveDirectoryMonitor(directoryPath: string) { recursiveDirectoryMonitor(directoryPath: string): void {
monitorFile(directoryPath, (_, eventType) => { monitorFile(directoryPath, (_, eventType) => {
if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) { if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
this.emit("changed"); this.emit('changed');
} }
}); });
const directory = Gio.File.new_for_path(directoryPath); const directory = Gio.File.new_for_path(directoryPath);
const enumerator = directory.enumerate_children( const enumerator = directory.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
"standard::*",
Gio.FileQueryInfoFlags.NONE,
null,
);
let fileInfo: FileInfo; let fileInfo: FileInfo;
while ((fileInfo = enumerator.next_file(null) as FileInfo) !== null) { while ((fileInfo = enumerator.next_file(null) as FileInfo) !== null) {
const childPath = directoryPath + "/" + fileInfo.get_name(); const childPath = directoryPath + '/' + fileInfo.get_name();
if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) { if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) {
this.recursiveDirectoryMonitor(childPath); this.recursiveDirectoryMonitor(childPath);
} }

9
globals.d.ts vendored
View File

@@ -1,13 +1,14 @@
// globals.d.ts // globals.d.ts
/* eslint-disable no-var */
import { Options, Variable as VariableType } from "types/variable"; import { Options, Variable as VariableType } from 'types/variable';
declare global { declare global {
var globalMousePos: VariableType<number[]>; var globalMousePos: VariableType<number[]>;
var useTheme: Function; var useTheme: (filePath: string) => void;
var globalWeatherVar: VariableType<Weather>; var globalWeatherVar: VariableType<Weather>;
var options: Options var options: Options;
var removingNotifications: VariableType<boolean>; var removingNotifications: VariableType<boolean>;
} }
export { }; export {};

View File

@@ -1,5 +1,5 @@
import { Variable as VariableType } from "types/variable"; import { Variable as VariableType } from 'types/variable';
const globalMousePosVar: VariableType<number[]> = Variable([0, 0]); const globalMousePosVar: VariableType<number[]> = Variable([0, 0]);
globalThis["globalMousePos"] = globalMousePosVar; globalThis['globalMousePos'] = globalMousePosVar;

View File

@@ -1,16 +1,15 @@
export const WIFI_STATUS_MAP = { export const WIFI_STATUS_MAP = {
unknown: "Status Unknown", unknown: 'Status Unknown',
unmanaged: "Unmanaged", unmanaged: 'Unmanaged',
unavailable: "Unavailable", unavailable: 'Unavailable',
disconnected: "Disconnected", disconnected: 'Disconnected',
prepare: "Preparing Connecting", prepare: 'Preparing Connecting',
config: "Connecting", config: 'Connecting',
need_auth: "Needs Authentication", need_auth: 'Needs Authentication',
ip_config: "Requesting IP", ip_config: 'Requesting IP',
ip_check: "Checking Access", ip_check: 'Checking Access',
secondaries: "Waiting on Secondaries", secondaries: 'Waiting on Secondaries',
activated: "Connected", activated: 'Connected',
deactivating: "Disconnecting", deactivating: 'Disconnecting',
failed: "Connection Failed", failed: 'Connection Failed',
} as const; } as const;

View File

@@ -1,37 +1,37 @@
import icons from "modules/icons/index"; import icons from 'modules/icons/index';
import { Notification } from "types/service/notifications"; import { Notification } from 'types/service/notifications';
export const removingNotifications = Variable<boolean>(false); export const removingNotifications = Variable<boolean>(false);
export const getNotificationIcon = (app_name: string, app_icon: string, app_entry: string) => { export const getNotificationIcon = (app_name: string, app_icon: string, app_entry: string): string => {
let icon: string = icons.fallback.notification; let icon: string = icons.fallback.notification;
if (Utils.lookUpIcon(app_name) || Utils.lookUpIcon(app_name.toLowerCase() || "")) { if (Utils.lookUpIcon(app_name) || Utils.lookUpIcon(app_name.toLowerCase() || '')) {
icon = Utils.lookUpIcon(app_name) icon = Utils.lookUpIcon(app_name)
? app_name ? app_name
: Utils.lookUpIcon(app_name.toLowerCase()) : Utils.lookUpIcon(app_name.toLowerCase())
? app_name.toLowerCase() ? app_name.toLowerCase()
: ""; : '';
} }
if (Utils.lookUpIcon(app_icon) && icon === "") { if (Utils.lookUpIcon(app_icon) && icon === '') {
icon = app_icon; icon = app_icon;
} }
if (Utils.lookUpIcon(app_entry || "") && icon === "") { if (Utils.lookUpIcon(app_entry || '') && icon === '') {
icon = app_entry || ""; icon = app_entry || '';
} }
return icon; return icon;
}; };
export const closeNotifications = async (notifications: Notification[]) => { export const closeNotifications = async (notifications: Notification[]): Promise<void> => {
removingNotifications.value = true; removingNotifications.value = true;
for (const notif of notifications) { for (const notif of notifications) {
notif.close(); notif.close();
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
} }
removingNotifications.value = false; removingNotifications.value = false;
} };
globalThis["removingNotifications"] = removingNotifications; globalThis['removingNotifications'] = removingNotifications;

View File

@@ -1,12 +1,12 @@
import Gio from "gi://Gio" import Gio from 'gi://Gio';
import { bash, Notify } from "lib/utils"; import { bash, Notify } from 'lib/utils';
import icons from "lib/icons" import icons from 'lib/icons';
import { filterConfigForThemeOnly, loadJsonFile, saveConfigToFile } from "widget/settings/shared/FileChooser"; import { filterConfigForThemeOnly, loadJsonFile, saveConfigToFile } from 'widget/settings/shared/FileChooser';
export const hexColorPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/; export const hexColorPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/;
globalThis.useTheme = (filePath: string): void => { globalThis.useTheme = (filePath: string): void => {
let importedConfig = loadJsonFile(filePath); const importedConfig = loadJsonFile(filePath);
if (!importedConfig) { if (!importedConfig) {
return; return;
@@ -16,22 +16,22 @@ globalThis.useTheme = (filePath: string): void => {
summary: `Importing Theme`, summary: `Importing Theme`,
body: `Importing: ${filePath}`, body: `Importing: ${filePath}`,
iconName: icons.ui.info, iconName: icons.ui.info,
timeout: 7000 timeout: 7000,
}); });
let tmpConfigFile = Gio.File.new_for_path(`${TMP}/config.json`); const tmpConfigFile = Gio.File.new_for_path(`${TMP}/config.json`);
let optionsConfigFile = Gio.File.new_for_path(OPTIONS); const optionsConfigFile = Gio.File.new_for_path(OPTIONS);
let [tmpSuccess, tmpContent] = tmpConfigFile.load_contents(null); const [tmpSuccess, tmpContent] = tmpConfigFile.load_contents(null);
let [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null); const [optionsSuccess, optionsContent] = optionsConfigFile.load_contents(null);
if (!tmpSuccess || !optionsSuccess) { if (!tmpSuccess || !optionsSuccess) {
console.error("Failed to read existing configuration files."); console.error('Failed to read existing configuration files.');
return; return;
} }
let tmpConfig = JSON.parse(new TextDecoder("utf-8").decode(tmpContent)); let tmpConfig = JSON.parse(new TextDecoder('utf-8').decode(tmpContent));
let optionsConfig = JSON.parse(new TextDecoder("utf-8").decode(optionsContent)); let optionsConfig = JSON.parse(new TextDecoder('utf-8').decode(optionsContent));
const filteredConfig = filterConfigForThemeOnly(importedConfig); const filteredConfig = filterConfigForThemeOnly(importedConfig);
tmpConfig = { ...tmpConfig, ...filteredConfig }; tmpConfig = { ...tmpConfig, ...filteredConfig };
@@ -39,6 +39,5 @@ globalThis.useTheme = (filePath: string): void => {
saveConfigToFile(tmpConfig, `${TMP}/config.json`); saveConfigToFile(tmpConfig, `${TMP}/config.json`);
saveConfigToFile(optionsConfig, OPTIONS); saveConfigToFile(optionsConfig, OPTIONS);
bash("pkill ags && ags"); bash('pkill ags && ags');
} };

View File

@@ -1,13 +1,13 @@
import { Opt } from "lib/option"; import { Opt } from 'lib/option';
import { HexColor, RecursiveOptionsObject } from "lib/types/options"; import { HexColor, RecursiveOptionsObject } from 'lib/types/options';
export const isOpt = <T>(value: unknown): value is Opt<T> => export const isOpt = <T>(value: unknown): value is Opt<T> =>
typeof value === 'object' && value !== null && 'value' in value && value instanceof Opt; typeof value === 'object' && value !== null && 'value' in value && value instanceof Opt;
export const isRecursiveOptionsObject = (value: unknown): value is RecursiveOptionsObject => { export const isRecursiveOptionsObject = (value: unknown): value is RecursiveOptionsObject => {
return typeof value === 'object' && value !== null && !Array.isArray(value); return typeof value === 'object' && value !== null && !Array.isArray(value);
} };
export const isHexColor = (value: string): value is HexColor => { export const isHexColor = (value: string): value is HexColor => {
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value); return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value);
} };

View File

@@ -1,8 +1,8 @@
import options from "options"; import options from 'options';
import { UnitType, Weather, WeatherIconTitle, WeatherIcon } from "lib/types/weather.js"; import { UnitType, Weather, WeatherIconTitle, WeatherIcon } from 'lib/types/weather.js';
import { DEFAULT_WEATHER } from "lib/types/defaults/weather.js"; import { DEFAULT_WEATHER } from 'lib/types/defaults/weather.js';
import GLib from "gi://GLib?version=2.0"; import GLib from 'gi://GLib?version=2.0';
import { weatherIcons } from "modules/icons/weather.js"; import { weatherIcons } from 'modules/icons/weather.js';
const { key, interval, location } = options.menus.clock.weather; const { key, interval, location } = options.menus.clock.weather;
@@ -10,12 +10,12 @@ export const globalWeatherVar = Variable<Weather>(DEFAULT_WEATHER);
let weatherIntervalInstance: null | number = null; let weatherIntervalInstance: null | number = null;
const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: string) => { const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: string): void => {
if (weatherIntervalInstance !== null) { if (weatherIntervalInstance !== null) {
GLib.source_remove(weatherIntervalInstance); GLib.source_remove(weatherIntervalInstance);
} }
const formattedLocation = loc.replace(" ", "%20"); const formattedLocation = loc.replace(' ', '%20');
weatherIntervalInstance = Utils.interval(weatherInterval, () => { weatherIntervalInstance = Utils.interval(weatherInterval, () => {
Utils.execAsync( Utils.execAsync(
@@ -23,13 +23,13 @@ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: str
) )
.then((res) => { .then((res) => {
try { try {
if (typeof res !== "string") { if (typeof res !== 'string') {
return (globalWeatherVar.value = DEFAULT_WEATHER); return (globalWeatherVar.value = DEFAULT_WEATHER);
} }
const parsedWeather = JSON.parse(res); const parsedWeather = JSON.parse(res);
if (Object.keys(parsedWeather).includes("error")) { if (Object.keys(parsedWeather).includes('error')) {
return (globalWeatherVar.value = DEFAULT_WEATHER); return (globalWeatherVar.value = DEFAULT_WEATHER);
} }
@@ -46,43 +46,41 @@ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: str
}); });
}; };
Utils.merge([key.bind("value"), interval.bind("value"), location.bind("value")], (weatherKey, weatherInterval, loc) => { Utils.merge([key.bind('value'), interval.bind('value'), location.bind('value')], (weatherKey, weatherInterval, loc) => {
if (!weatherKey) { if (!weatherKey) {
return (globalWeatherVar.value = DEFAULT_WEATHER); return (globalWeatherVar.value = DEFAULT_WEATHER);
} }
weatherIntervalFn(weatherInterval, loc, weatherKey); weatherIntervalFn(weatherInterval, loc, weatherKey);
}); });
export const getTemperature = (wthr: Weather, unt: UnitType) => { export const getTemperature = (wthr: Weather, unt: UnitType): string => {
if (unt === "imperial") { if (unt === 'imperial') {
return `${Math.ceil(wthr.current.temp_f)}° F`; return `${Math.ceil(wthr.current.temp_f)}° F`;
} else { } else {
return `${Math.ceil(wthr.current.temp_c)}° C`; return `${Math.ceil(wthr.current.temp_c)}° C`;
} }
}; };
export const getWeatherIcon = (fahren: number) => { export const getWeatherIcon = (fahren: number): Record<string, string> => {
const icons = { const icons = {
100: "", 100: '',
75: "", 75: '',
50: "", 50: '',
25: "", 25: '',
0: "", 0: '',
} as const; } as const;
const colors = { const colors = {
100: "weather-color red", 100: 'weather-color red',
75: "weather-color orange", 75: 'weather-color orange',
50: "weather-color lavender", 50: 'weather-color lavender',
25: "weather-color blue", 25: 'weather-color blue',
0: "weather-color sky", 0: 'weather-color sky',
} as const; } as const;
type IconKeys = keyof typeof icons; type IconKeys = keyof typeof icons;
const threshold: IconKeys = const threshold: IconKeys =
fahren < 0 fahren < 0 ? 0 : ([100, 75, 50, 25, 0] as IconKeys[]).find((threshold) => threshold <= fahren) || 0;
? 0
: ([100, 75, 50, 25, 0] as IconKeys[]).find((threshold) => threshold <= fahren) || 0;
const icon = icons[threshold || 50]; const icon = icons[threshold || 50];
const color = colors[threshold || 50]; const color = colors[threshold || 50];
@@ -93,35 +91,32 @@ export const getWeatherIcon = (fahren: number) => {
}; };
}; };
export const getWindConditions = (wthr: Weather, unt: UnitType) => { export const getWindConditions = (wthr: Weather, unt: UnitType): string => {
if (unt === "imperial") { if (unt === 'imperial') {
return `${Math.floor(wthr.current.wind_mph)} mph`; return `${Math.floor(wthr.current.wind_mph)} mph`;
} }
return `${Math.floor(wthr.current.wind_kph)} kph`; return `${Math.floor(wthr.current.wind_kph)} kph`;
}; };
export const getRainChance = (wthr: Weather) => `${wthr.forecast.forecastday[0].day.daily_chance_of_rain}%`; export const getRainChance = (wthr: Weather): string => `${wthr.forecast.forecastday[0].day.daily_chance_of_rain}%`;
export const isValidWeatherIconTitle = (title: string): title is WeatherIconTitle => { export const isValidWeatherIconTitle = (title: string): title is WeatherIconTitle => {
return title in weatherIcons; return title in weatherIcons;
}; };
export const getWeatherStatusTextIcon = (wthr: Weather): WeatherIcon => { export const getWeatherStatusTextIcon = (wthr: Weather): WeatherIcon => {
let iconQuery = wthr.current.condition.text let iconQuery = wthr.current.condition.text.trim().toLowerCase().replaceAll(' ', '_');
.trim()
.toLowerCase()
.replaceAll(" ", "_");
if (!wthr.current.is_day && iconQuery === "partly_cloudy") { if (!wthr.current.is_day && iconQuery === 'partly_cloudy') {
iconQuery = "partly_cloudy_night"; iconQuery = 'partly_cloudy_night';
} }
if (isValidWeatherIconTitle(iconQuery)) { if (isValidWeatherIconTitle(iconQuery)) {
return weatherIcons[iconQuery]; return weatherIcons[iconQuery];
} else { } else {
console.warn(`Unknown weather icon title: ${iconQuery}`); console.warn(`Unknown weather icon title: ${iconQuery}`);
return weatherIcons["warning"]; return weatherIcons['warning'];
} }
}; };
globalThis["globalWeatherVar"] = globalWeatherVar; globalThis['globalWeatherVar'] = globalWeatherVar;

View File

@@ -6,5 +6,5 @@ export const WINDOW_LAYOUTS: string[] = [
'top-left', 'top-left',
'bottom-left', 'bottom-left',
'bottom-center', 'bottom-center',
'bottom-right' 'bottom-right',
]; ];

View File

@@ -1,145 +1,145 @@
export const substitutes = { export const substitutes = {
"transmission-gtk": "transmission", 'transmission-gtk': 'transmission',
"blueberry.py": "blueberry", 'blueberry.py': 'blueberry',
"Caprine": "facebook-messenger", Caprine: 'facebook-messenger',
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic", 'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic", 'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
"audio-headset-bluetooth": "audio-headphones-symbolic", 'audio-headset-bluetooth': 'audio-headphones-symbolic',
"audio-card-analog-usb": "audio-speakers-symbolic", 'audio-card-analog-usb': 'audio-speakers-symbolic',
"audio-card-analog-pci": "audio-card-symbolic", 'audio-card-analog-pci': 'audio-card-symbolic',
"preferences-system": "emblem-system-symbolic", 'preferences-system': 'emblem-system-symbolic',
"com.github.Aylur.ags-symbolic": "controls-symbolic", 'com.github.Aylur.ags-symbolic': 'controls-symbolic',
"com.github.Aylur.ags": "controls-symbolic", 'com.github.Aylur.ags': 'controls-symbolic',
} as const; } as const;
export default { export default {
missing: "image-missing-symbolic", missing: 'image-missing-symbolic',
nix: { nix: {
nix: "nix-snowflake-symbolic", nix: 'nix-snowflake-symbolic',
}, },
app: { app: {
terminal: "terminal-symbolic", terminal: 'terminal-symbolic',
}, },
fallback: { fallback: {
executable: "application-x-executable", executable: 'application-x-executable',
notification: "dialog-information-symbolic", notification: 'dialog-information-symbolic',
video: "video-x-generic-symbolic", video: 'video-x-generic-symbolic',
audio: "audio-x-generic-symbolic", audio: 'audio-x-generic-symbolic',
}, },
ui: { ui: {
close: "window-close-symbolic", close: 'window-close-symbolic',
colorpicker: "color-select-symbolic", colorpicker: 'color-select-symbolic',
info: "info-symbolic", info: 'info-symbolic',
link: "external-link-symbolic", link: 'external-link-symbolic',
lock: "system-lock-screen-symbolic", lock: 'system-lock-screen-symbolic',
menu: "open-menu-symbolic", menu: 'open-menu-symbolic',
refresh: "view-refresh-symbolic", refresh: 'view-refresh-symbolic',
search: "system-search-symbolic", search: 'system-search-symbolic',
settings: "emblem-system-symbolic", settings: 'emblem-system-symbolic',
themes: "preferences-desktop-theme-symbolic", themes: 'preferences-desktop-theme-symbolic',
tick: "object-select-symbolic", tick: 'object-select-symbolic',
time: "hourglass-symbolic", time: 'hourglass-symbolic',
toolbars: "toolbars-symbolic", toolbars: 'toolbars-symbolic',
warning: "dialog-warning-symbolic", warning: 'dialog-warning-symbolic',
avatar: "avatar-default-symbolic", avatar: 'avatar-default-symbolic',
arrow: { arrow: {
right: "pan-end-symbolic", right: 'pan-end-symbolic',
left: "pan-start-symbolic", left: 'pan-start-symbolic',
down: "pan-down-symbolic", down: 'pan-down-symbolic',
up: "pan-up-symbolic", up: 'pan-up-symbolic',
}, },
}, },
audio: { audio: {
mic: { mic: {
muted: "microphone-disabled-symbolic", muted: 'microphone-disabled-symbolic',
low: "microphone-sensitivity-low-symbolic", low: 'microphone-sensitivity-low-symbolic',
medium: "microphone-sensitivity-medium-symbolic", medium: 'microphone-sensitivity-medium-symbolic',
high: "microphone-sensitivity-high-symbolic", high: 'microphone-sensitivity-high-symbolic',
}, },
volume: { volume: {
muted: "audio-volume-muted-symbolic", muted: 'audio-volume-muted-symbolic',
low: "audio-volume-low-symbolic", low: 'audio-volume-low-symbolic',
medium: "audio-volume-medium-symbolic", medium: 'audio-volume-medium-symbolic',
high: "audio-volume-high-symbolic", high: 'audio-volume-high-symbolic',
overamplified: "audio-volume-overamplified-symbolic", overamplified: 'audio-volume-overamplified-symbolic',
}, },
type: { type: {
headset: "audio-headphones-symbolic", headset: 'audio-headphones-symbolic',
speaker: "audio-speakers-symbolic", speaker: 'audio-speakers-symbolic',
card: "audio-card-symbolic", card: 'audio-card-symbolic',
}, },
mixer: "mixer-symbolic", mixer: 'mixer-symbolic',
}, },
powerprofile: { powerprofile: {
balanced: "power-profile-balanced-symbolic", balanced: 'power-profile-balanced-symbolic',
"power-saver": "power-profile-power-saver-symbolic", 'power-saver': 'power-profile-power-saver-symbolic',
performance: "power-profile-performance-symbolic", performance: 'power-profile-performance-symbolic',
}, },
asusctl: { asusctl: {
profile: { profile: {
Balanced: "power-profile-balanced-symbolic", Balanced: 'power-profile-balanced-symbolic',
Quiet: "power-profile-power-saver-symbolic", Quiet: 'power-profile-power-saver-symbolic',
Performance: "power-profile-performance-symbolic", Performance: 'power-profile-performance-symbolic',
}, },
mode: { mode: {
Integrated: "processor-symbolic", Integrated: 'processor-symbolic',
Hybrid: "controller-symbolic", Hybrid: 'controller-symbolic',
}, },
}, },
battery: { battery: {
charging: "battery-flash-symbolic", charging: 'battery-flash-symbolic',
warning: "battery-empty-symbolic", warning: 'battery-empty-symbolic',
}, },
bluetooth: { bluetooth: {
enabled: "bluetooth-active-symbolic", enabled: 'bluetooth-active-symbolic',
disabled: "bluetooth-disabled-symbolic", disabled: 'bluetooth-disabled-symbolic',
}, },
brightness: { brightness: {
indicator: "display-brightness-symbolic", indicator: 'display-brightness-symbolic',
keyboard: "keyboard-brightness-symbolic", keyboard: 'keyboard-brightness-symbolic',
screen: "display-brightness-symbolic", screen: 'display-brightness-symbolic',
}, },
powermenu: { powermenu: {
sleep: "weather-clear-night-symbolic", sleep: 'weather-clear-night-symbolic',
reboot: "system-reboot-symbolic", reboot: 'system-reboot-symbolic',
logout: "system-log-out-symbolic", logout: 'system-log-out-symbolic',
shutdown: "system-shutdown-symbolic", shutdown: 'system-shutdown-symbolic',
}, },
recorder: { recorder: {
recording: "media-record-symbolic", recording: 'media-record-symbolic',
}, },
notifications: { notifications: {
noisy: "org.gnome.Settings-notifications-symbolic", noisy: 'org.gnome.Settings-notifications-symbolic',
silent: "notifications-disabled-symbolic", silent: 'notifications-disabled-symbolic',
message: "chat-bubbles-symbolic", message: 'chat-bubbles-symbolic',
}, },
trash: { trash: {
full: "user-trash-full-symbolic", full: 'user-trash-full-symbolic',
empty: "user-trash-symbolic", empty: 'user-trash-symbolic',
}, },
mpris: { mpris: {
shuffle: { shuffle: {
enabled: "media-playlist-shuffle-symbolic", enabled: 'media-playlist-shuffle-symbolic',
disabled: "media-playlist-consecutive-symbolic", disabled: 'media-playlist-consecutive-symbolic',
}, },
loop: { loop: {
none: "media-playlist-repeat-symbolic", none: 'media-playlist-repeat-symbolic',
track: "media-playlist-repeat-song-symbolic", track: 'media-playlist-repeat-song-symbolic',
playlist: "media-playlist-repeat-symbolic", playlist: 'media-playlist-repeat-symbolic',
}, },
playing: "media-playback-pause-symbolic", playing: 'media-playback-pause-symbolic',
paused: "media-playback-start-symbolic", paused: 'media-playback-start-symbolic',
stopped: "media-playback-start-symbolic", stopped: 'media-playback-start-symbolic',
prev: "media-skip-backward-symbolic", prev: 'media-skip-backward-symbolic',
next: "media-skip-forward-symbolic", next: 'media-skip-forward-symbolic',
}, },
system: { system: {
cpu: "org.gnome.SystemMonitor-symbolic", cpu: 'org.gnome.SystemMonitor-symbolic',
ram: "drive-harddisk-solidstate-symbolic", ram: 'drive-harddisk-solidstate-symbolic',
temp: "temperature-symbolic", temp: 'temperature-symbolic',
}, },
color: { color: {
dark: "dark-mode-symbolic", dark: 'dark-mode-symbolic',
light: "light-mode-symbolic", light: 'light-mode-symbolic',
}, },
} };

View File

@@ -1,70 +1,73 @@
import { isHexColor } from "globals/variables" import { isHexColor } from 'globals/variables';
import { Variable } from "resource:///com/github/Aylur/ags/variable.js" import { Variable } from 'resource:///com/github/Aylur/ags/variable.js';
import { MkOptionsResult } from './types/options';
type OptProps = { type OptProps = {
persistent?: boolean persistent?: boolean;
} };
export class Opt<T = unknown> extends Variable<T> { export class Opt<T = unknown> extends Variable<T> {
static { Service.register(this) } static {
Service.register(this);
}
constructor(initial: T, { persistent = false }: OptProps = {}) { constructor(initial: T, { persistent = false }: OptProps = {}) {
super(initial) super(initial);
this.initial = initial this.initial = initial;
this.persistent = persistent this.persistent = persistent;
} }
initial: T initial: T;
id = "" id = '';
persistent: boolean persistent: boolean;
toString() { return `${this.value}` } toString(): string {
toJSON() { return `opt:${this.value}` } return `${this.value}`;
}
toJSON(): string {
return `opt:${this.value}`;
}
getValue = (): T => { getValue = (): T => {
return super.getValue() return super.getValue();
};
init(cacheFile: string): void {
const cacheV = JSON.parse(Utils.readFile(cacheFile) || '{}')[this.id];
if (cacheV !== undefined) this.value = cacheV;
this.connect('changed', () => {
const cache = JSON.parse(Utils.readFile(cacheFile) || '{}');
cache[this.id] = this.value;
Utils.writeFileSync(JSON.stringify(cache, null, 2), cacheFile);
});
} }
init(cacheFile: string) { reset(): string | undefined {
const cacheV = JSON.parse(Utils.readFile(cacheFile) || "{}")[this.id] if (this.persistent) return;
if (cacheV !== undefined)
this.value = cacheV
this.connect("changed", () => {
const cache = JSON.parse(Utils.readFile(cacheFile) || "{}")
cache[this.id] = this.value
Utils.writeFileSync(JSON.stringify(cache, null, 2), cacheFile)
})
}
reset() {
if (this.persistent)
return;
if (JSON.stringify(this.value) !== JSON.stringify(this.initial)) { if (JSON.stringify(this.value) !== JSON.stringify(this.initial)) {
this.value = this.initial this.value = this.initial;
return this.id; return this.id;
} }
} }
doResetColor() { doResetColor(): string | undefined {
if (this.persistent) if (this.persistent) return;
return;
const isColor = isHexColor(this.value as string); const isColor = isHexColor(this.value as string);
if ((JSON.stringify(this.value) !== JSON.stringify(this.initial)) && isColor) { if (JSON.stringify(this.value) !== JSON.stringify(this.initial) && isColor) {
this.value = this.initial this.value = this.initial;
return this.id return this.id;
} }
return; return;
} }
} }
export const opt = <T>(initial: T, opts?: OptProps) => new Opt(initial, opts) export const opt = <T>(initial: T, opts?: OptProps): Opt<T> => new Opt(initial, opts);
function getOptions(object: Record<string, unknown>, path = ""): Opt[] { const getOptions = (object: Record<string, unknown>, path = ''): Opt[] => {
return Object.keys(object).flatMap(key => { return Object.keys(object).flatMap((key) => {
const obj = object[key]; const obj = object[key];
const id = path ? path + "." + key : key; const id = path ? path + '.' + key : key;
if (obj instanceof Variable) { if (obj instanceof Variable) {
const optValue = obj as Opt; const optValue = obj as Opt;
@@ -72,74 +75,73 @@ function getOptions(object: Record<string, unknown>, path = ""): Opt[] {
return optValue; return optValue;
} }
if (typeof obj === "object" && obj !== null) { if (typeof obj === 'object' && obj !== null) {
return getOptions(obj as Record<string, unknown>, id); // Recursively process nested objects return getOptions(obj as Record<string, unknown>, id); // Recursively process nested objects
} }
return []; return [];
}); });
} };
export function mkOptions<T extends object>(cacheFile: string, object: T, confFile: string = "config.json") { export function mkOptions<T extends object>(
for (const opt of getOptions(object as Record<string, unknown>)) cacheFile: string,
opt.init(cacheFile) object: T,
confFile: string = 'config.json',
): T & MkOptionsResult<T> {
for (const opt of getOptions(object as Record<string, unknown>)) opt.init(cacheFile);
Utils.ensureDirectory(cacheFile.split("/").slice(0, -1).join("/")) Utils.ensureDirectory(cacheFile.split('/').slice(0, -1).join('/'));
const configFile = `${TMP}/${confFile}` const configFile = `${TMP}/${confFile}`;
const values = getOptions(object as Record<string, unknown>).reduce((obj, { id, value }) => ({ [id]: value, ...obj }), {}) const values = getOptions(object as Record<string, unknown>).reduce(
Utils.writeFileSync(JSON.stringify(values, null, 2), configFile) (obj, { id, value }) => ({ [id]: value, ...obj }),
{},
);
Utils.writeFileSync(JSON.stringify(values, null, 2), configFile);
Utils.monitorFile(configFile, () => { Utils.monitorFile(configFile, () => {
const cache = JSON.parse(Utils.readFile(configFile) || "{}") const cache = JSON.parse(Utils.readFile(configFile) || '{}');
for (const opt of getOptions(object as Record<string, unknown>)) { for (const opt of getOptions(object as Record<string, unknown>)) {
if (JSON.stringify(cache[opt.id]) !== JSON.stringify(opt.value)) if (JSON.stringify(cache[opt.id]) !== JSON.stringify(opt.value)) opt.value = cache[opt.id];
opt.value = cache[opt.id]
} }
}) });
function sleep(ms = 0): Promise<T> { function sleep(ms = 0): Promise<T> {
return new Promise(r => setTimeout(r, ms)) return new Promise((r) => setTimeout(r, ms));
} }
async function reset( const reset = async (
[opt, ...list] = getOptions(object as Record<string, unknown>), [opt, ...list] = getOptions(object as Record<string, unknown>),
id = opt?.reset(), id = opt?.reset(),
): Promise<Array<string>> { ): Promise<Array<string>> => {
if (!opt) if (!opt) return sleep().then(() => []);
return sleep().then(() => [])
return id return id ? [id, ...(await sleep(50).then(() => reset(list)))] : await sleep().then(() => reset(list));
? [id, ...(await sleep(50).then(() => reset(list)))] };
: await sleep().then(() => reset(list))
}
async function resetTheme( const resetTheme = async (
[opt, ...list] = getOptions(object as Record<string, unknown>), [opt, ...list] = getOptions(object as Record<string, unknown>),
id = opt?.doResetColor(), id = opt?.doResetColor(),
): Promise<Array<string>> { ): Promise<Array<string>> => {
if (!opt) if (!opt) return sleep().then(() => []);
return sleep().then(() => [])
return id return id
? [id, ...(await sleep(50).then(() => resetTheme(list)))] ? [id, ...(await sleep(50).then(() => resetTheme(list)))]
: await sleep().then(() => resetTheme(list)) : await sleep().then(() => resetTheme(list));
} };
return Object.assign(object, { return Object.assign(object, {
configFile, configFile,
array: () => getOptions(object as Record<string, unknown>), array: () => getOptions(object as Record<string, unknown>),
async reset() { async reset() {
return (await reset()).join("\n") return (await reset()).join('\n');
}, },
async resetTheme() { async resetTheme() {
return (await resetTheme()).join("\n") return (await resetTheme()).join('\n');
}, },
handler(deps: string[], callback: () => void) { handler(deps: string[], callback: () => void) {
for (const opt of getOptions(object as Record<string, unknown>)) { for (const opt of getOptions(object as Record<string, unknown>)) {
if (deps.some(i => opt.id.startsWith(i))) if (deps.some((i) => opt.id.startsWith(i))) opt.connect('changed', callback);
opt.connect("changed", callback)
} }
}, },
}) });
} }

View File

@@ -1,16 +1,16 @@
import GLib from "gi://GLib?version=2.0" import GLib from 'gi://GLib?version=2.0';
declare global { declare global {
const OPTIONS: string const OPTIONS: string;
const TMP: string const TMP: string;
const USER: string const USER: string;
} }
Object.assign(globalThis, { Object.assign(globalThis, {
OPTIONS: `${GLib.get_user_cache_dir()}/ags/hyprpanel/options.json`, OPTIONS: `${GLib.get_user_cache_dir()}/ags/hyprpanel/options.json`,
TMP: `${GLib.get_tmp_dir()}/ags/hyprpanel`, TMP: `${GLib.get_tmp_dir()}/ags/hyprpanel`,
USER: GLib.get_user_name(), USER: GLib.get_user_name(),
}) });
Utils.ensureDirectory(TMP) Utils.ensureDirectory(TMP);
App.addIcons(`${App.configDir}/assets`) App.addIcons(`${App.configDir}/assets`);

View File

@@ -1,5 +1,5 @@
import { MprisPlayer } from "types/service/mpris"; import { MprisPlayer } from 'types/service/mpris';
const mpris = await Service.import("mpris"); const mpris = await Service.import('mpris');
export const getCurrentPlayer = (activePlayer: MprisPlayer = mpris.players[0]): MprisPlayer => { export const getCurrentPlayer = (activePlayer: MprisPlayer = mpris.players[0]): MprisPlayer => {
const statusOrder = { const statusOrder = {
@@ -12,18 +12,12 @@ export const getCurrentPlayer = (activePlayer: MprisPlayer = mpris.players[0]):
return mpris.players[0]; return mpris.players[0];
} }
const isPlaying = mpris.players.some( const isPlaying = mpris.players.some((p: MprisPlayer) => p.play_back_status === 'Playing');
(p: MprisPlayer) => p.play_back_status === "Playing",
);
const playerStillExists = mpris.players.some( const playerStillExists = mpris.players.some((p) => activePlayer.bus_name === p.bus_name);
(p) => activePlayer.bus_name === p.bus_name
);
const nextPlayerUp = mpris.players.sort( const nextPlayerUp = mpris.players.sort(
(a: MprisPlayer, b: MprisPlayer) => (a: MprisPlayer, b: MprisPlayer) => statusOrder[a.play_back_status] - statusOrder[b.play_back_status],
statusOrder[a.play_back_status] -
statusOrder[b.play_back_status],
)[0]; )[0];
if (isPlaying || !playerStillExists) { if (isPlaying || !playerStillExists) {
@@ -31,4 +25,4 @@ export const getCurrentPlayer = (activePlayer: MprisPlayer = mpris.players[0]):
} }
return activePlayer; return activePlayer;
} };

View File

@@ -1,10 +1,7 @@
import { Notification } from "types/service/notifications"; import { Notification } from 'types/service/notifications';
export const filterNotifications = (notifications: Notification[], filter: string[]): Notification[] => { export const filterNotifications = (notifications: Notification[], filter: string[]): Notification[] => {
const notifFilter = new Set(filter.map((name: string) => name.toLowerCase().replace(/\s+/g, '_')));
const notifFilter = new Set(
filter.map((name: string) => name.toLowerCase().replace(/\s+/g, '_'))
);
const filteredNotifications = notifications.filter((notif: Notification) => { const filteredNotifications = notifications.filter((notif: Notification) => {
const normalizedAppName = notif.app_name.toLowerCase().replace(/\s+/g, '_'); const normalizedAppName = notif.app_name.toLowerCase().replace(/\s+/g, '_');
@@ -12,4 +9,4 @@ export const filterNotifications = (notifications: Notification[], filter: strin
}); });
return filteredNotifications; return filteredNotifications;
} };

5
lib/types/audio.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
export type InputDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
type DummyDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
type RealPlaybackDevices = Button<Box<Box<Label<Attribute>, Attribute>, Attribute>, Attribute>[];
export type PlaybackDevices = DummyDevices | RealPlaybackDevices;

58
lib/types/bar.d.ts vendored
View File

@@ -1,43 +1,45 @@
import { Binding, Connectable } from "types/service" import { Binding, Connectable } from 'types/service';
import { Variable } from "types/variable" import { Variable } from 'types/variable';
import Box from "types/widgets/box"; import Box from 'types/widgets/box';
import Label from "types/widgets/label"; import Button from 'types/widgets/button';
import { Widget as WidgetType } from "types/widgets/widget" import Label from 'types/widgets/label';
import { Attribute, Child } from './widget';
export type Child = { export type BarBoxChild = {
component: Box<Gtk.Widget, unknown>; component: Box<Gtk.Widget, unknown>;
isVisible?: boolean; isVisible?: boolean;
isVis?: Variable<boolean>; isVis?: Variable<boolean>;
boxClass: string; boxClass: string;
props: ButtonProps; } & ButtonProps;
};
export type SelfButton = Button<Child, Attribute>;
export type BoxHook = (self: Box<Gtk.Widget, Gtk.Widget>) => void; export type BoxHook = (self: Box<Gtk.Widget, Gtk.Widget>) => void;
export type LabelHook = (self: Label<Gtk.Widget>) => void; export type LabelHook = (self: Label<Gtk.Widget>) => void;
export type Module = { export type Module = {
icon?: string | Binding<string>, icon?: string | Binding<string>;
textIcon?: string | Binding<string>, textIcon?: string | Binding<string>;
label?: string | Binding<string>, label?: string | Binding<string>;
labelHook?: LabelHook, labelHook?: LabelHook;
boundLabel?: string, boundLabel?: string;
tooltipText?: string | Binding<string>, tooltipText?: string | Binding<string>;
boxClass: string, boxClass: string;
props?: ButtonProps, props?: ButtonProps;
showLabel?: boolean, showLabel?: boolean;
showLabelBinding?: Binding, showLabelBinding?: Binding;
hook?: BoxHook, hook?: BoxHook;
connection?: Binding<Connectable> connection?: Binding<Connectable>;
} };
export type ResourceLabelType = "used/total" | "used" | "percentage" | "free"; export type ResourceLabelType = 'used/total' | 'used' | 'percentage' | 'free';
export type StorageIcon = "󰋊" | "" | "󱛟" | "" | "" | ""; export type StorageIcon = '󰋊' | '' | '󱛟' | '' | '' | '';
export type NetstatIcon = "󰖟" | "󰇚" | "󰕒" | "󰛳" | "" | "󰣺" | "󰖩" | "" | "󰈀"; export type NetstatIcon = '󰖟' | '󰇚' | '󰕒' | '󰛳' | '' | '󰣺' | '󰖩' | '' | '󰈀';
export type NetstatLabelType = "full" | "in" | "out"; export type NetstatLabelType = 'full' | 'in' | 'out';
export type RateUnit = "GiB" | "MiB" | "KiB" | "auto"; export type RateUnit = 'GiB' | 'MiB' | 'KiB' | 'auto';
export type UpdatesIcon = "󰚰" | "󰇚" | "" | "󱑢" | "󱑣" | "󰏖" | "" | "󰏔" | "󰏗"; export type UpdatesIcon = '󰚰' | '󰇚' | '' | '󱑢' | '󱑣' | '󰏖' | '' | '󰏔' | '󰏗';
export type PowerIcon = "" | "" | "󰍃" | "󰿅" | "󰒲" | "󰤄"; export type PowerIcon = '' | '' | '󰍃' | '󰿅' | '󰒲' | '󰤄';

View File

@@ -1,6 +1,13 @@
export type GenericResourceData = { export type GenericFunction<T, P extends unknown[] = unknown[]> = (...args: P) => T;
export type GenericResourceMetrics = {
total: number; total: number;
used: number; used: number;
free: number;
percentage: number; percentage: number;
} };
type GenericResourceData = ResourceUsage & {
free: number;
};
export type Postfix = 'TiB' | 'GiB' | 'MiB' | 'KiB' | 'B';

View File

@@ -1,7 +1,7 @@
import { layoutMap } from "customModules/kblayout/layouts"; import { layoutMap } from 'customModules/kblayout/layouts';
export type KbLabelType = "layout" | "code"; export type KbLabelType = 'layout' | 'code';
export type KbIcon = "" | "󰌌" | "" | "󰬴" | "󰗊"; export type KbIcon = '' | '󰌌' | '' | '󰬴' | '󰗊';
export type HyprctlKeyboard = { export type HyprctlKeyboard = {
address: string; address: string;
@@ -24,10 +24,10 @@ export type HyprctlMouse = {
export type HyprctlDeviceLayout = { export type HyprctlDeviceLayout = {
mice: HyprctlMouse[]; mice: HyprctlMouse[];
keyboards: HyprctlKeyboard[]; keyboards: HyprctlKeyboard[];
tablets: any[]; tablets: unknown[];
touch: any[]; touch: unknown[];
switches: any[]; switches: unknown[];
}; };
export type LayoutKeys = keyof typeof layoutMap; export type LayoutKeys = keyof typeof layoutMap;
export type LayoutValues = typeof layoutMap[LayoutKeys]; export type LayoutValues = (typeof layoutMap)[LayoutKeys];

View File

@@ -1,5 +1,4 @@
export type NetworkResourceData = { export type NetworkResourceData = {
in: string; in: string;
out: string; out: string;
} };

View File

@@ -1,9 +1,9 @@
import { Binding } from "lib/utils"; import { Binding } from 'lib/utils';
export type InputHandlerEvents = { export type InputHandlerEvents = {
onPrimaryClick?: Binding, onPrimaryClick?: Binding;
onSecondaryClick?: Binding, onSecondaryClick?: Binding;
onMiddleClick?: Binding, onMiddleClick?: Binding;
onScrollUp?: Binding, onScrollUp?: Binding;
onScrollDown?: Binding, onScrollDown?: Binding;
} };

View File

@@ -1,5 +1,5 @@
import { NetstatLabelType, ResourceLabelType } from "../bar"; import { NetstatLabelType, ResourceLabelType } from '../bar';
export const LABEL_TYPES: ResourceLabelType[] = ["used/total", "used", "free", "percentage"]; export const LABEL_TYPES: ResourceLabelType[] = ['used/total', 'used', 'free', 'percentage'];
export const NETWORK_LABEL_TYPES: NetstatLabelType[] = ["full", "in", "out"]; export const NETWORK_LABEL_TYPES: NetstatLabelType[] = ['full', 'in', 'out'];

View File

@@ -1,10 +1,10 @@
import { RateUnit } from "../bar"; import { RateUnit } from '../bar';
import { NetworkResourceData } from "../customModules/network"; import { NetworkResourceData } from '../customModules/network';
export const GET_DEFAULT_NETSTAT_DATA = (dataType: RateUnit): NetworkResourceData => { export const GET_DEFAULT_NETSTAT_DATA = (dataType: RateUnit): NetworkResourceData => {
if (dataType === 'auto') { if (dataType === 'auto') {
return { in: `0 Kib/s`, out: `0 Kib/s` } return { in: `0 Kib/s`, out: `0 Kib/s` };
} }
return { in: `0 ${dataType}/s`, out: `0 ${dataType}/s` } return { in: `0 ${dataType}/s`, out: `0 ${dataType}/s` };
}; };

View File

@@ -1,60 +1,60 @@
export const defaultColorMap = { export const defaultColorMap = {
"rosewater": "#f5e0dc", rosewater: '#f5e0dc',
"flamingo": "#f2cdcd", flamingo: '#f2cdcd',
"pink": "#f5c2e7", pink: '#f5c2e7',
"mauve": "#cba6f7", mauve: '#cba6f7',
"red": "#f38ba8", red: '#f38ba8',
"maroon": "#eba0ac", maroon: '#eba0ac',
"peach": "#fab387", peach: '#fab387',
"yellow": "#f9e2af", yellow: '#f9e2af',
"green": "#a6e3a1", green: '#a6e3a1',
"teal": "#94e2d5", teal: '#94e2d5',
"sky": "#89dceb", sky: '#89dceb',
"sapphire": "#74c7ec", sapphire: '#74c7ec',
"blue": "#89b4fa", blue: '#89b4fa',
"lavender": "#b4befe", lavender: '#b4befe',
"text": "#cdd6f4", text: '#cdd6f4',
"subtext1": "#bac2de", subtext1: '#bac2de',
"subtext2": "#a6adc8", subtext2: '#a6adc8',
"overlay2": "#9399b2", overlay2: '#9399b2',
"overlay1": "#7f849c", overlay1: '#7f849c',
"overlay0": "#6c7086", overlay0: '#6c7086',
"surface2": "#585b70", surface2: '#585b70',
"surface1": "#45475a", surface1: '#45475a',
"surface0": "#313244", surface0: '#313244',
"base2": "#242438", base2: '#242438',
"base": "#1e1e2e", base: '#1e1e2e',
"mantle": "#181825", mantle: '#181825',
"crust": "#11111b", crust: '#11111b',
"surface1_2": "#454759", surface1_2: '#454759',
"text2": "#cdd6f3", text2: '#cdd6f3',
"pink2": "#f5c2e6", pink2: '#f5c2e6',
"red2": "#f38ba7", red2: '#f38ba7',
"peach2": "#fab386", peach2: '#fab386',
"mantle2": "#181824", mantle2: '#181824',
"surface0_2": "#313243", surface0_2: '#313243',
"surface2_2": "#585b69", surface2_2: '#585b69',
"overlay1_2": "#7f849b", overlay1_2: '#7f849b',
"lavender2": "#b4befd", lavender2: '#b4befd',
"mauve2": "#cba6f6", mauve2: '#cba6f6',
"green2": "#a6e3a0", green2: '#a6e3a0',
"sky2": "#89dcea", sky2: '#89dcea',
"teal2": "#94e2d4", teal2: '#94e2d4',
"yellow2": "#f9e2ad", yellow2: '#f9e2ad',
"maroon2": "#eba0ab", maroon2: '#eba0ab',
"crust2": "#11111a", crust2: '#11111a',
"pink3": "#f5c2e8", pink3: '#f5c2e8',
"red3": "#f38ba9", red3: '#f38ba9',
"mantle3": "#181826", mantle3: '#181826',
"surface0_3": "#313245", surface0_3: '#313245',
"surface2_3": "#585b71", surface2_3: '#585b71',
"overlay1_3": "#7f849d", overlay1_3: '#7f849d',
"lavender3": "#b4beff", lavender3: '#b4beff',
"mauve3": "#cba6f8", mauve3: '#cba6f8',
"green3": "#a6e3a2", green3: '#a6e3a2',
"sky3": "#89dcec", sky3: '#89dcec',
"teal3": "#94e2d6", teal3: '#94e2d6',
"yellow3": "#f9e2ae", yellow3: '#f9e2ae',
"maroon3": "#eba0ad", maroon3: '#eba0ad',
"crust3": "#11111c", crust3: '#11111c',
}; } as const;

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
import { WindowProps } from "types/widgets/window"; import { WindowProps } from 'types/widgets/window';
import { GtkWidget, Transition } from './widget';
export type DropdownMenuProps = { export type DropdownMenuProps = {
name: string; name: string;
child: any; child: GtkWidget;
layout?: string; layout?: string;
transition?: any; transition?: Transition;
exclusivity?: Exclusivity; exclusivity?: Exclusivity;
fixed?: boolean; fixed?: boolean;
} & WindowProps; } & WindowProps;

View File

@@ -1,3 +1,3 @@
export type Config = { export type Config = {
[key: string]: string | number | boolean | object; [key: string]: string | number | boolean | object;
} };

View File

@@ -12,14 +12,14 @@ export type GPU_Stat = {
index: number; index: number;
uuid: string; uuid: string;
name: string; name: string;
"temperature.gpu": number; 'temperature.gpu': number;
"fan.speed": number; 'fan.speed': number;
"utilization.gpu": number; 'utilization.gpu': number;
"utilization.enc": number; 'utilization.enc': number;
"utilization.dec": number; 'utilization.dec': number;
"power.draw": number; 'power.draw': number;
"enforced.power.limit": number; 'enforced.power.limit': number;
"memory.used": number; 'memory.used': number;
"memory.total": number; 'memory.total': number;
processes: Process[]; processes: Process[];
}; };

View File

@@ -1,3 +1,2 @@
export type LoopStatus = 'none' | 'track' | 'playlist'; export type LoopStatus = 'none' | 'track' | 'playlist';
export type PlaybackStatus = 'playing' | 'paused' | 'stopped'; export type PlaybackStatus = 'playing' | 'paused' | 'stopped';

View File

@@ -1,4 +1,4 @@
import { WIFI_STATUS_MAP } from "globals/network"; import { WIFI_STATUS_MAP } from 'globals/network';
export type AccessPoint = { export type AccessPoint = {
bssid: string | null; bssid: string | null;
@@ -9,6 +9,8 @@ export type AccessPoint = {
strength: number; strength: number;
frequency: number; frequency: number;
iconName: string | undefined; iconName: string | undefined;
} };
export type WifiStatus = keyof typeof WIFI_STATUS_MAP; export type WifiStatus = keyof typeof WIFI_STATUS_MAP;
export type WifiIcon = '󰤩' | '󰤨' | '󰤪' | '󰤨' | '󰤩' | '󰤮' | '󰤨' | '󰤥' | '󰤢' | '󰤟' | '󰤯';

View File

@@ -1,4 +1,4 @@
import icons from "modules/icons/index"; import icons from 'modules/icons/index';
export interface NotificationArgs { export interface NotificationArgs {
appName?: string; appName?: string;

301
lib/types/options.d.ts vendored
View File

@@ -1,126 +1,213 @@
import { Opt } from "lib/option"; import { Opt } from 'lib/option';
import { Variable } from "types/variable"; import { Variable } from 'types/variable';
import { defaultColorMap } from './defaults/options';
export type MkOptionsResult<T> = {
configFile: string;
array: () => Opt[];
reset: () => Promise<string>;
resetTheme: () => Promise<string>;
handler: (deps: string[], callback: () => void) => void;
};
export type RecursiveOptionsObject = { export type RecursiveOptionsObject = {
[key: string]: RecursiveOptionsObject | Opt<string | number | boolean> | Opt<any>; [key: string]: RecursiveOptionsObject | Opt<string | number | boolean> | Opt<any>;
}; };
export type Unit = "imperial" | "metric"; export type Unit = 'imperial' | 'metric';
export type PowerOptions = "sleep" | "reboot" | "logout" | "shutdown"; export type PowerOptions = 'sleep' | 'reboot' | 'logout' | 'shutdown';
export type NotificationAnchor = "top" | "top right" | "top left" | "bottom" | "bottom right" | "bottom left" | "left" | "right"; export type NotificationAnchor =
export type OSDAnchor = "top left" | "top" | "top right" | "right" | "bottom right" | "bottom" | "bottom left" | "left"; | 'top'
export type BarButtonStyles = "default" | "split" | "wave" | "wave2"; | 'top right'
| 'top left'
| 'bottom'
| 'bottom right'
| 'bottom left'
| 'left'
| 'right';
export type OSDAnchor = 'top left' | 'top' | 'top right' | 'right' | 'bottom right' | 'bottom' | 'bottom left' | 'left';
export type BarButtonStyles = 'default' | 'split' | 'wave' | 'wave2';
export type ThemeExportData = { export type ThemeExportData = {
filePath: string, filePath: string;
themeOnly: boolean themeOnly: boolean;
} };
export type RowProps<T> = { export type RowProps<T> = {
opt: Opt<T> opt: Opt<T>;
title: string title: string;
note?: string note?: string;
type?: type?:
| "number" | 'number'
| "color" | 'color'
| "float" | 'float'
| "object" | 'object'
| "string" | 'string'
| "enum" | 'enum'
| "boolean" | 'boolean'
| "img" | 'img'
| "wallpaper" | 'wallpaper'
| "export" | 'export'
| "import" | 'import'
| "config_import" | 'config_import'
| "font" | 'font';
enums?: string[] enums?: T[];
max?: number max?: number;
min?: number min?: number;
disabledBinding?: Variable<boolean> disabledBinding?: Variable<boolean>;
exportData?: ThemeExportData exportData?: ThemeExportData;
subtitle?: string | VarType<any> | Opt, subtitle?: string | VarType<any> | Opt;
subtitleLink?: string, subtitleLink?: string;
dependencies?: string[], dependencies?: string[];
increment?: number increment?: number;
} };
export type OSDOrientation = "horizontal" | "vertical"; export type OSDOrientation = 'horizontal' | 'vertical';
export type HexColor = `#${string}`; export type HexColor = `#${string}`;
export type WindowLayer = "top" | "bottom" | "overlay" | "background"; export type WindowLayer = 'top' | 'bottom' | 'overlay' | 'background';
export type ActiveWsIndicator = 'underline' | 'highlight' | 'color';
export type MatugenColors = { export type MatugenColors = {
"background": HexColor, background: HexColor;
"error": HexColor, error: HexColor;
"error_container": HexColor, error_container: HexColor;
"inverse_on_surface": HexColor, inverse_on_surface: HexColor;
"inverse_primary": HexColor, inverse_primary: HexColor;
"inverse_surface": HexColor, inverse_surface: HexColor;
"on_background": HexColor, on_background: HexColor;
"on_error": HexColor, on_error: HexColor;
"on_error_container": HexColor, on_error_container: HexColor;
"on_primary": HexColor, on_primary: HexColor;
"on_primary_container": HexColor, on_primary_container: HexColor;
"on_primary_fixed": HexColor, on_primary_fixed: HexColor;
"on_primary_fixed_variant": HexColor, on_primary_fixed_variant: HexColor;
"on_secondary": HexColor, on_secondary: HexColor;
"on_secondary_container": HexColor, on_secondary_container: HexColor;
"on_secondary_fixed": HexColor, on_secondary_fixed: HexColor;
"on_secondary_fixed_variant": HexColor, on_secondary_fixed_variant: HexColor;
"on_surface": HexColor, on_surface: HexColor;
"on_surface_variant": HexColor, on_surface_variant: HexColor;
"on_tertiary": HexColor, on_tertiary: HexColor;
"on_tertiary_container": HexColor, on_tertiary_container: HexColor;
"on_tertiary_fixed": HexColor, on_tertiary_fixed: HexColor;
"on_tertiary_fixed_variant": HexColor, on_tertiary_fixed_variant: HexColor;
"outline": HexColor, outline: HexColor;
"outline_variant": HexColor, outline_variant: HexColor;
"primary": HexColor, primary: HexColor;
"primary_container": HexColor, primary_container: HexColor;
"primary_fixed": HexColor, primary_fixed: HexColor;
"primary_fixed_dim": HexColor, primary_fixed_dim: HexColor;
"scrim": HexColor, scrim: HexColor;
"secondary": HexColor, secondary: HexColor;
"secondary_container": HexColor, secondary_container: HexColor;
"secondary_fixed": HexColor, secondary_fixed: HexColor;
"secondary_fixed_dim": HexColor, secondary_fixed_dim: HexColor;
"shadow": HexColor, shadow: HexColor;
"surface": HexColor, surface: HexColor;
"surface_bright": HexColor, surface_bright: HexColor;
"surface_container": HexColor, surface_container: HexColor;
"surface_container_high": HexColor, surface_container_high: HexColor;
"surface_container_highest": HexColor, surface_container_highest: HexColor;
"surface_container_low": HexColor, surface_container_low: HexColor;
"surface_container_lowest": HexColor, surface_container_lowest: HexColor;
"surface_dim": HexColor, surface_dim: HexColor;
"surface_variant": HexColor, surface_variant: HexColor;
"tertiary": HexColor, tertiary: HexColor;
"tertiary_container": HexColor, tertiary_container: HexColor;
"tertiary_fixed": HexColor, tertiary_fixed: HexColor;
"tertiary_fixed_dim": HexColor tertiary_fixed_dim: HexColor;
} };
type MatugenScheme = export type MatugenVariation = {
| "content" rosewater: HexColor;
| "expressive" flamingo: HexColor;
| "fidelity" pink: HexColor;
| "fruit-salad" mauve: HexColor;
| "monochrome" red: HexColor;
| "neutral" maroon: HexColor;
| "rainbow" peach: HexColor;
| "tonal-spot"; yellow: HexColor;
green: HexColor;
teal: HexColor;
sky: HexColor;
sapphire: HexColor;
blue: HexColor;
lavender: HexColor;
text: HexColor;
subtext1: HexColor;
subtext2: HexColor;
overlay2: HexColor;
overlay1: HexColor;
overlay0: HexColor;
surface2: HexColor;
surface1: HexColor;
surface0: HexColor;
base2: HexColor;
base: HexColor;
mantle: HexColor;
crust: HexColor;
notifications_closer: HexColor;
notifications_background: HexColor;
dashboard_btn_text: HexColor;
red2: HexColor;
peach2: HexColor;
pink2: HexColor;
mantle2: HexColor;
surface1_2: HexColor;
surface0_2: HexColor;
overlay1_2: HexColor;
text2: HexColor;
lavender2: HexColor;
crust2: HexColor;
maroon2: HexColor;
mauve2: HexColor;
green2: HexColor;
surface2_2: HexColor;
sky2: HexColor;
teal2: HexColor;
yellow2: HexColor;
pink3: HexColor;
red3: HexColor;
mantle3: HexColor;
surface0_3: HexColor;
surface2_3: HexColor;
overlay1_3: HexColor;
lavender3: HexColor;
mauve3: HexColor;
green3: HexColor;
sky3: HexColor;
teal3: HexColor;
yellow3: HexColor;
maroon3: HexColor;
crust3: HexColor;
notifications_closer?: HexColor;
notifications_background?: HexColor;
dashboard_btn_text?: HexColor;
};
export type MatugenScheme =
| 'content'
| 'expressive'
| 'fidelity'
| 'fruit-salad'
| 'monochrome'
| 'neutral'
| 'rainbow'
| 'tonal-spot';
type MatugenVariation = export type MatugenVariations =
| "standard_1" | 'standard_1'
| "standard_2" | 'standard_2'
| "standard_3" | 'standard_3'
| "monochrome_1" | 'monochrome_1'
| "monochrome_2" | 'monochrome_2'
| "monochrome_3" | 'monochrome_3'
| "vivid_1" | 'vivid_1'
| "vivid_2" | 'vivid_2'
| "vivid_3" | 'vivid_3';
type MatugenTheme = "light" | "dark"; type MatugenTheme = 'light' | 'dark';
export type ColorMapKey = keyof typeof defaultColorMap;
export type ColorMapValue = (typeof defaultColorMap)[ColorMapKey];

View File

@@ -1,6 +1,6 @@
import { Widget } from "types/widgets/widget"; import { Widget } from 'types/widgets/widget';
import { WindowProps } from "types/widgets/window"; import { WindowProps } from 'types/widgets/window';
import { Transition } from "./widget"; import { Transition } from './widget';
export type PopupWindowProps = { export type PopupWindowProps = {
name: string; name: string;
@@ -13,15 +13,23 @@ export type PopupWindowProps = {
export type LayoutFunction = ( export type LayoutFunction = (
name: string, name: string,
child: Widget, child: Widget,
transition: Transition transition: Transition,
) => { ) => {
center: () => Widget; center: () => Widget;
top: () => Widget; top: () => Widget;
"top-right": () => Widget; 'top-right': () => Widget;
"top-center": () => Widget; 'top-center': () => Widget;
"top-left": () => Widget; 'top-left': () => Widget;
"bottom-left": () => Widget; 'bottom-left': () => Widget;
"bottom-center": () => Widget; 'bottom-center': () => Widget;
"bottom-right": () => Widget; 'bottom-right': () => Widget;
}; };
export type Layouts = 'center' | 'top' | 'top-right' | 'top-center' | 'top-left' | 'bottom-left' | 'bottom-center' | 'bottom-right'; export type Layouts =
| 'center'
| 'top'
| 'top-right'
| 'top-center'
| 'top-left'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right';

View File

@@ -1 +1 @@
export type Action = "sleep" | "reboot" | "logout" | "shutdown"; export type Action = 'sleep' | 'reboot' | 'logout' | 'shutdown';

View File

@@ -1,8 +1,8 @@
import icons from "modules/icons/index"; import icons from 'modules/icons/index';
import PowerProfiles from "types/service/powerprofiles.js" import PowerProfiles from 'types/service/powerprofiles.js';
export type PowerProfiles = InstanceType<typeof PowerProfiles>; export type PowerProfiles = InstanceType<typeof PowerProfiles>;
export type PowerProfile = "power-saver" | "balanced" | "performance"; export type PowerProfile = 'power-saver' | 'balanced' | 'performance';
export type PowerProfileObject = { export type PowerProfileObject = {
[key: string]: string; [key: string]: string;
} };

0
lib/types/systray.d.ts vendored Normal file
View File

View File

@@ -1,3 +1,6 @@
import { substitutes } from "lib/icons"; import { substitutes } from 'lib/icons';
type SubstituteKeys = keyof typeof substitutes; type SubstituteKeys = keyof typeof substitutes;
export type ThrottleFn = (cmd: string, fn: ((output: string) => void) | undefined) => void;
export type ThrottleFnCallback = ((output: string) => void) | undefined;

1
lib/types/variable.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export type Bind = OriginalBinding<GObject.Object, keyof Props<GObject.Object>, unknown>;

View File

@@ -1,3 +1,3 @@
export type VolumeIcons = { export type VolumeIcons = {
[index: number]: string [index: number]: string;
} };

View File

@@ -1,12 +1,12 @@
import { weatherIcons } from "modules/icons/weather"; import { weatherIcons } from 'modules/icons/weather';
export type UnitType = "imperial" | "metric"; export type UnitType = 'imperial' | 'metric';
export type Weather = { export type Weather = {
location: Location; location: Location;
current: Current; current: Current;
forecast: Forecast; forecast: Forecast;
} };
export type Current = { export type Current = {
last_updated_epoch?: number; last_updated_epoch?: number;
@@ -45,17 +45,17 @@ export type Current = {
chance_of_rain?: number; chance_of_rain?: number;
will_it_snow?: number; will_it_snow?: number;
chance_of_snow?: number; chance_of_snow?: number;
} };
export type Condition = { export type Condition = {
text: string; text: string;
icon: string; icon: string;
code: number; code: number;
} };
export type Forecast = { export type Forecast = {
forecastday: Forecastday[]; forecastday: Forecastday[];
} };
export type Forecastday = { export type Forecastday = {
date: string; date: string;
@@ -63,7 +63,7 @@ export type Forecastday = {
day: Day; day: Day;
astro: Astro; astro: Astro;
hour: Current[]; hour: Current[];
} };
export type Astro = { export type Astro = {
sunrise: string; sunrise: string;
@@ -74,7 +74,7 @@ export type Astro = {
moon_illumination: number; moon_illumination: number;
is_moon_up: number; is_moon_up: number;
is_sun_up: number; is_sun_up: number;
} };
export type Day = { export type Day = {
maxtemp_c: number; maxtemp_c: number;
@@ -97,7 +97,7 @@ export type Day = {
daily_chance_of_snow: number; daily_chance_of_snow: number;
condition: Condition; condition: Condition;
uv: number; uv: number;
} };
export type Location = { export type Location = {
name: string; name: string;
@@ -108,11 +108,11 @@ export type Location = {
tz_id: string; tz_id: string;
localtime_epoch: number; localtime_epoch: number;
localtime: string; localtime: string;
} };
export type TemperatureIconColorMap = { export type TemperatureIconColorMap = {
[key: number]: string; [key: number]: string;
} };
export type WeatherIconTitle = keyof typeof weatherIcons; export type WeatherIconTitle = keyof typeof weatherIcons;
export type WeatherIcon = typeof weatherIcons[WeatherIconTitle]; export type WeatherIcon = (typeof weatherIcons)[WeatherIconTitle];

32
lib/types/widget.d.ts vendored
View File

@@ -1,6 +1,28 @@
export type Exclusivity = 'normal' | 'ignore' | 'exclusive'; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
export type Anchor = "left" | "right" | "top" | "down"; import Box from 'types/widgets/box';
export type Transition = "none" | "crossfade" | "slide_right" | "slide_left" | "slide_up" | "slide_down";
// Window export type Exclusivity = 'normal' | 'ignore' | 'exclusive';
export type Layouts = 'center' | 'top' | 'top-right' | 'top-center' | 'top-left' | 'bottom-left' | 'bottom-center' | 'bottom-right'; export type Anchor = 'left' | 'right' | 'top' | 'down';
export type Transition = 'none' | 'crossfade' | 'slide_right' | 'slide_left' | 'slide_up' | 'slide_down';
export type Layouts =
| 'center'
| 'top'
| 'top-right'
| 'top-center'
| 'top-left'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right';
export type Attribute = unknown;
export type Child = Gtk.Widget;
export type GtkWidget = Gtk.Widget;
export type BoxWidget = Box<GtkWidget, Child>;
export type GButton = Gtk.Button;
export type GBox = Gtk.Box;
export type GLabel = Gtk.Label;
export type GCenterBox = Gtk.Box;
export type EventHandler<Self> = (self: Self, event: Gdk.Event) => boolean | unknown;

View File

@@ -1,8 +1,12 @@
export type WorkspaceRule = { export type WorkspaceRule = {
workspaceString: string, workspaceString: string;
monitor: string, monitor: string;
} };
export type WorkspaceMap = { export type WorkspaceMap = {
[key: string]: number[], [key: string]: number[];
} };
export type MonitorMap = {
[key: number]: string;
};

View File

@@ -1,29 +1,27 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { type Application } from "types/service/applications" import { type Application } from 'types/service/applications';
import { NotificationAnchor } from "./types/options" import { NotificationAnchor } from './types/options';
import { OSDAnchor } from "lib/types/options"; import { OSDAnchor } from 'lib/types/options';
import icons, { substitutes } from "./icons" import icons, { substitutes } from './icons';
import Gtk from "gi://Gtk?version=3.0" import Gtk from 'gi://Gtk?version=3.0';
import Gdk from "gi://Gdk" import Gdk from 'gi://Gdk';
import GLib from "gi://GLib?version=2.0" import GLib from 'gi://GLib?version=2.0';
import GdkPixbuf from "gi://GdkPixbuf"; import GdkPixbuf from 'gi://GdkPixbuf';
import { NotificationArgs } from "types/utils/notify" import { NotificationArgs } from 'types/utils/notify';
import { SubstituteKeys } from "./types/utils"; import { SubstituteKeys } from './types/utils';
import { Window } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
export type Binding<T> = import("types/service").Binding<any, any, T>
export type Binding<T> = import('types/service').Binding<any, any, T>;
/** /**
* @returns substitute icon || name || fallback icon * @returns substitute icon || name || fallback icon
*/ */
export function icon(name: string | null, fallback = icons.missing) { export function icon(name: string | null, fallback = icons.missing): string {
const validateSubstitute = (name: string): name is SubstituteKeys => name in substitutes; const validateSubstitute = (name: string): name is SubstituteKeys => name in substitutes;
if (!name) if (!name) return fallback || '';
return fallback || ""
if (GLib.file_test(name, GLib.FileTest.EXISTS)) if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
return name
let icon: string = name; let icon: string = name;
@@ -31,38 +29,36 @@ export function icon(name: string | null, fallback = icons.missing) {
icon = substitutes[name]; icon = substitutes[name];
} }
if (Utils.lookUpIcon(icon)) if (Utils.lookUpIcon(icon)) return icon;
return icon
print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`) print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`);
return fallback return fallback;
} }
/** /**
* @returns execAsync(["bash", "-c", cmd]) * @returns execAsync(["bash", "-c", cmd])
*/ */
export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]) { export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]): Promise<string> {
const cmd = typeof strings === "string" ? strings : strings const cmd =
.flatMap((str, i) => str + `${values[i] ?? ""}`) typeof strings === 'string' ? strings : strings.flatMap((str, i) => str + `${values[i] ?? ''}`).join('');
.join("")
return Utils.execAsync(["bash", "-c", cmd]).catch(err => { return Utils.execAsync(['bash', '-c', cmd]).catch((err) => {
console.error(cmd, err) console.error(cmd, err);
return "" return '';
}) });
} }
/** /**
* @returns execAsync(cmd) * @returns execAsync(cmd)
*/ */
export async function sh(cmd: string | string[]) { export async function sh(cmd: string | string[]): Promise<string> {
return Utils.execAsync(cmd).catch(err => { return Utils.execAsync(cmd).catch((err) => {
console.error(typeof cmd === "string" ? cmd : cmd.join(" "), err) console.error(typeof cmd === 'string' ? cmd : cmd.join(' '), err);
return "" return '';
}) });
} }
export function forMonitors(widget: (monitor: number) => Gtk.Window) { export function forMonitors(widget: (monitor: number) => Gtk.Window): Window[] {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1; const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).flatMap(widget); return range(n, 0).flatMap(widget);
} }
@@ -70,64 +66,62 @@ export function forMonitors(widget: (monitor: number) => Gtk.Window) {
/** /**
* @returns [start...length] * @returns [start...length]
*/ */
export function range(length: number, start = 1) { export function range(length: number, start = 1): number[] {
return Array.from({ length }, (_, i) => i + start) return Array.from({ length }, (_, i) => i + start);
} }
/** /**
* @returns true if all of the `bins` are found * @returns true if all of the `bins` are found
*/ */
export function dependencies(...bins: string[]) { export function dependencies(...bins: string[]): boolean {
const missing = bins.filter(bin => Utils.exec({ const missing = bins.filter((bin) =>
Utils.exec({
cmd: `which ${bin}`, cmd: `which ${bin}`,
out: () => false, out: () => false,
err: () => true, err: () => true,
})) }),
);
if (missing.length > 0) { if (missing.length > 0) {
console.warn(Error(`missing dependencies: ${missing.join(", ")}`)) console.warn(Error(`missing dependencies: ${missing.join(', ')}`));
Notify({ Notify({
summary: "Dependencies not found!", summary: 'Dependencies not found!',
body: `The following dependencies are missing: ${missing.join(", ")}`, body: `The following dependencies are missing: ${missing.join(', ')}`,
iconName: icons.ui.warning, iconName: icons.ui.warning,
timeout: 7000 timeout: 7000,
}); });
} }
return missing.length === 0 return missing.length === 0;
} }
/** /**
* run app detached * run app detached
*/ */
export function launchApp(app: Application) { export function launchApp(app: Application): void {
const exe = app.executable const exe = app.executable
.split(/\s+/) .split(/\s+/)
.filter(str => !str.startsWith("%") && !str.startsWith("@")) .filter((str) => !str.startsWith('%') && !str.startsWith('@'))
.join(" ") .join(' ');
bash(`${exe} &`) bash(`${exe} &`);
app.frequency += 1 app.frequency += 1;
} }
/** /**
* to use with drag and drop * to use with drag and drop
*/ */
export function createSurfaceFromWidget(widget: Gtk.Widget) { export function createSurfaceFromWidget(widget: Gtk.Widget): GdkPixbuf.Pixbuf {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const cairo = imports.gi.cairo as any const cairo = imports.gi.cairo as any;
const alloc = widget.get_allocation() const alloc = widget.get_allocation();
const surface = new cairo.ImageSurface( const surface = new cairo.ImageSurface(cairo.Format.ARGB32, alloc.width, alloc.height);
cairo.Format.ARGB32, const cr = new cairo.Context(surface);
alloc.width, cr.setSourceRGBA(255, 255, 255, 0);
alloc.height, cr.rectangle(0, 0, alloc.width, alloc.height);
) cr.fill();
const cr = new cairo.Context(surface) widget.draw(cr);
cr.setSourceRGBA(255, 255, 255, 0) return surface;
cr.rectangle(0, 0, alloc.width, alloc.height)
cr.fill()
widget.draw(cr)
return surface
} }
/** /**
@@ -138,9 +132,10 @@ export const isAnImage = (imgFilePath: string): boolean => {
GdkPixbuf.Pixbuf.new_from_file(imgFilePath); GdkPixbuf.Pixbuf.new_from_file(imgFilePath);
return true; return true;
} catch (error) { } catch (error) {
console.error(error);
return false; return false;
} }
} };
export const Notify = (notifPayload: NotificationArgs): void => { export const Notify = (notifPayload: NotificationArgs): void => {
let command = 'notify-send'; let command = 'notify-send';
@@ -154,20 +149,20 @@ export const Notify = (notifPayload: NotificationArgs): void => {
if (notifPayload.transient) command += ` -e`; if (notifPayload.transient) command += ` -e`;
if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`; if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`;
Utils.execAsync(command) Utils.execAsync(command);
} };
export function getPosition(pos: NotificationAnchor | OSDAnchor): ("top" | "bottom" | "left" | "right")[] { export function getPosition(pos: NotificationAnchor | OSDAnchor): ('top' | 'bottom' | 'left' | 'right')[] {
const positionMap: { [key: string]: ("top" | "bottom" | "left" | "right")[] } = { const positionMap: { [key: string]: ('top' | 'bottom' | 'left' | 'right')[] } = {
"top": ["top"], top: ['top'],
"top right": ["top", "right"], 'top right': ['top', 'right'],
"top left": ["top", "left"], 'top left': ['top', 'left'],
"bottom": ["bottom"], bottom: ['bottom'],
"bottom right": ["bottom", "right"], 'bottom right': ['bottom', 'right'],
"bottom left": ["bottom", "left"], 'bottom left': ['bottom', 'left'],
"right": ["right"], right: ['right'],
"left": ["left"], left: ['left'],
}; };
return positionMap[pos] || ["top"]; return positionMap[pos] || ['top'];
} }

View File

@@ -1,16 +1,15 @@
import GLib from "gi://GLib" import GLib from 'gi://GLib';
import { DateTime } from 'types/@girs/glib-2.0/glib-2.0.cjs';
export const clock = Variable(GLib.DateTime.new_now_local(), { export const clock = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()], poll: [1000, (): DateTime => GLib.DateTime.new_now_local()],
}) });
export const uptime = Variable(0, { export const uptime = Variable(0, {
poll: [60_000, "cat /proc/uptime", line => poll: [60_000, 'cat /proc/uptime', (line): number => Number.parseInt(line.split('.')[0]) / 60],
Number.parseInt(line.split(".")[0]) / 60, });
],
})
export const distro = { export const distro = {
id: GLib.get_os_info("ID"), id: GLib.get_os_info('ID'),
logo: GLib.get_os_info("LOGO"), logo: GLib.get_os_info('LOGO'),
} };

30
main.ts
View File

@@ -1,27 +1,21 @@
import "lib/session"; import 'lib/session';
import "scss/style"; import 'scss/style';
import "globals/useTheme"; import 'globals/useTheme';
import "globals/mousePos"; import 'globals/mousePos';
import { Bar } from "modules/bar/Bar"; import { Bar } from 'modules/bar/Bar';
import MenuWindows from "./modules/menus/main.js"; import MenuWindows from './modules/menus/main.js';
import SettingsDialog from "widget/settings/SettingsDialog"; import SettingsDialog from 'widget/settings/SettingsDialog';
import Notifications from "./modules/notifications/index.js"; import Notifications from './modules/notifications/index.js';
import { forMonitors } from "lib/utils"; import { forMonitors } from 'lib/utils';
import OSD from "modules/osd/index"; import OSD from 'modules/osd/index';
App.config({ App.config({
onConfigParsed: () => Utils.execAsync(`python3 ${App.configDir}/services/bluetooth.py`), onConfigParsed: () => Utils.execAsync(`python3 ${App.configDir}/services/bluetooth.py`),
windows: [ windows: [...MenuWindows, Notifications(), SettingsDialog(), ...forMonitors(Bar), OSD()],
...MenuWindows,
Notifications(),
SettingsDialog(),
...forMonitors(Bar),
OSD(),
],
closeWindowDelay: { closeWindowDelay: {
sideright: 350, sideright: 350,
launcher: 350, launcher: 350,
bar0: 350, bar0: 350,
}, },
}) });

View File

@@ -1,8 +1,10 @@
const hyprland = await Service.import("hyprland"); const hyprland = await Service.import('hyprland');
import { import {
Menu, Menu,
Workspaces, ClientTitle, Media, Workspaces,
ClientTitle,
Media,
Notifications, Notifications,
Volume, Volume,
Network, Network,
@@ -20,54 +22,57 @@ import {
Updates, Updates,
Weather, Weather,
Power, Power,
} from "./Exports" } from './Exports';
import { BarItemBox as WidgetContainer } from "../shared/barItemBox.js"; import { BarItemBox as WidgetContainer } from '../shared/barItemBox.js';
import options from "options"; import options from 'options';
import Gdk from "gi://Gdk?version=3.0"; import Gdk from 'gi://Gdk?version=3.0';
import Button from "types/widgets/button.js"; import Button from 'types/widgets/button.js';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0.js"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0.js';
import './SideEffects'; import './SideEffects';
import { WindowLayer } from "lib/types/options.js"; import { WindowLayer } from 'lib/types/options.js';
import { Attribute, Child } from 'lib/types/widget.js';
import Window from 'types/widgets/window.js';
const { layouts } = options.bar; const { layouts } = options.bar;
export type BarWidget = keyof typeof widget; export type BarWidget = keyof typeof widget;
type Section = "battery" type Section =
| "dashboard" | 'battery'
| "workspaces" | 'dashboard'
| "windowtitle" | 'workspaces'
| "media" | 'windowtitle'
| "notifications" | 'media'
| "volume" | 'notifications'
| "network" | 'volume'
| "bluetooth" | 'network'
| "clock" | 'bluetooth'
| "ram" | 'clock'
| "cpu" | 'ram'
| "storage" | 'cpu'
| "netstat" | 'storage'
| "kbinput" | 'netstat'
| "updates" | 'kbinput'
| "weather" | 'updates'
| "power" | 'weather'
| "systray"; | 'power'
| 'systray';
type Layout = { type Layout = {
left: Section[], left: Section[];
middle: Section[], middle: Section[];
right: Section[], right: Section[];
} };
type BarLayout = { type BarLayout = {
[key: string]: Layout [key: string]: Layout;
} };
const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => { const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
const matchingKey = Object.keys(layouts).find(key => key === monitor.toString()); const matchingKey = Object.keys(layouts).find((key) => key === monitor.toString());
const wildcard = Object.keys(layouts).find(key => key === "*"); const wildcard = Object.keys(layouts).find((key) => key === '*');
if (matchingKey) { if (matchingKey) {
return layouts[matchingKey]; return layouts[matchingKey];
@@ -78,61 +83,47 @@ const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
} }
return { return {
left: [ left: ['dashboard', 'workspaces', 'windowtitle'],
"dashboard", middle: ['media'],
"workspaces", right: ['volume', 'network', 'bluetooth', 'battery', 'systray', 'clock', 'notifications'],
"windowtitle"
],
middle: [
"media"
],
right: [
"volume",
"network",
"bluetooth",
"battery",
"systray",
"clock",
"notifications"
]
}; };
} };
const widget = { const widget = {
battery: () => WidgetContainer(BatteryLabel()), battery: (): Button<Child, Attribute> => WidgetContainer(BatteryLabel()),
dashboard: () => WidgetContainer(Menu()), dashboard: (): Button<Child, Attribute> => WidgetContainer(Menu()),
workspaces: (monitor: number) => WidgetContainer(Workspaces(monitor)), workspaces: (monitor: number): Button<Child, Attribute> => WidgetContainer(Workspaces(monitor)),
windowtitle: () => WidgetContainer(ClientTitle()), windowtitle: (): Button<Child, Attribute> => WidgetContainer(ClientTitle()),
media: () => WidgetContainer(Media()), media: (): Button<Child, Attribute> => WidgetContainer(Media()),
notifications: () => WidgetContainer(Notifications()), notifications: (): Button<Child, Attribute> => WidgetContainer(Notifications()),
volume: () => WidgetContainer(Volume()), volume: (): Button<Child, Attribute> => WidgetContainer(Volume()),
network: () => WidgetContainer(Network()), network: (): Button<Child, Attribute> => WidgetContainer(Network()),
bluetooth: () => WidgetContainer(Bluetooth()), bluetooth: (): Button<Child, Attribute> => WidgetContainer(Bluetooth()),
clock: () => WidgetContainer(Clock()), clock: (): Button<Child, Attribute> => WidgetContainer(Clock()),
systray: () => WidgetContainer(SysTray()), systray: (): Button<Child, Attribute> => WidgetContainer(SysTray()),
ram: () => WidgetContainer(Ram()), ram: (): Button<Child, Attribute> => WidgetContainer(Ram()),
cpu: () => WidgetContainer(Cpu()), cpu: (): Button<Child, Attribute> => WidgetContainer(Cpu()),
storage: () => WidgetContainer(Storage()), storage: (): Button<Child, Attribute> => WidgetContainer(Storage()),
netstat: () => WidgetContainer(Netstat()), netstat: (): Button<Child, Attribute> => WidgetContainer(Netstat()),
kbinput: () => WidgetContainer(KbInput()), kbinput: (): Button<Child, Attribute> => WidgetContainer(KbInput()),
updates: () => WidgetContainer(Updates()), updates: (): Button<Child, Attribute> => WidgetContainer(Updates()),
weather: () => WidgetContainer(Weather()), weather: (): Button<Child, Attribute> => WidgetContainer(Weather()),
power: () => WidgetContainer(Power()), power: (): Button<Child, Attribute> => WidgetContainer(Power()),
}; };
type GdkMonitors = { type GdkMonitors = {
[key: string]: { [key: string]: {
key: string, key: string;
model: string, model: string;
used: boolean used: boolean;
} };
}; };
function getGdkMonitors(): GdkMonitors { function getGdkMonitors(): GdkMonitors {
const display = Gdk.Display.get_default(); const display = Gdk.Display.get_default();
if (display === null) { if (display === null) {
console.error("Failed to get Gdk display."); console.error('Failed to get Gdk display.');
return {}; return {};
} }
@@ -200,7 +191,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitors = getGdkMonitors(); const gdkMonitors = getGdkMonitors();
if (Object.keys(gdkMonitors).length === 0) { if (Object.keys(gdkMonitors).length === 0) {
console.error("No GDK monitors were found."); console.error('No GDK monitors were found.');
return monitor; return monitor;
} }
@@ -208,7 +199,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitor = gdkMonitors[monitor]; const gdkMonitor = gdkMonitors[monitor];
// First pass: Strict matching including the monitor index (i.e., hypMon.id === monitor + resolution+scale criteria) // First pass: Strict matching including the monitor index (i.e., hypMon.id === monitor + resolution+scale criteria)
const directMatch = hyprland.monitors.find(hypMon => { const directMatch = hyprland.monitors.find((hypMon) => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`; const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor; return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
}); });
@@ -219,7 +210,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
} }
// Second pass: Relaxed matching without considering the monitor index // Second pass: Relaxed matching without considering the monitor index
const hyprlandMonitor = hyprland.monitors.find(hypMon => { const hyprlandMonitor = hyprland.monitors.find((hypMon) => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`; const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id); return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id);
}); });
@@ -230,7 +221,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
} }
// Fallback: Find the first available monitor ID that hasn't been used // Fallback: Find the first available monitor ID that hasn't been used
const fallbackMonitor = hyprland.monitors.find(hypMon => !usedHyprlandMonitors.has(hypMon.id)); const fallbackMonitor = hyprland.monitors.find((hypMon) => !usedHyprlandMonitors.has(hypMon.id));
if (fallbackMonitor) { if (fallbackMonitor) {
usedHyprlandMonitors.add(fallbackMonitor.id); usedHyprlandMonitors.add(fallbackMonitor.id);
@@ -253,62 +244,63 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
export const Bar = (() => { export const Bar = (() => {
const usedHyprlandMonitors = new Set<number>(); const usedHyprlandMonitors = new Set<number>();
return (monitor: number) => { return (monitor: number): Window<Child, Attribute> => {
const hyprlandMonitor = gdkMonitorIdToHyprlandId(monitor, usedHyprlandMonitors); const hyprlandMonitor = gdkMonitorIdToHyprlandId(monitor, usedHyprlandMonitors);
return Widget.Window({ return Widget.Window({
name: `bar-${hyprlandMonitor}`, name: `bar-${hyprlandMonitor}`,
class_name: "bar", class_name: 'bar',
monitor, monitor,
visible: true, visible: true,
anchor: ["top", "left", "right"], anchor: ['top', 'left', 'right'],
exclusivity: "exclusive", exclusivity: 'exclusive',
layer: Utils.merge( layer: Utils.merge(
[ [options.theme.bar.layer.bind('value'), options.tear.bind('value')],
options.theme.bar.layer.bind("value"), (barLayer: WindowLayer, tear: boolean) => {
options.tear.bind("value") if (tear && barLayer === 'overlay') {
], return 'top';
(
barLayer: WindowLayer,
tear: boolean
) => {
if (tear && barLayer === "overlay") {
return "top";
} }
return barLayer; return barLayer;
}), },
),
child: Widget.Box({ child: Widget.Box({
class_name: 'bar-panel-container', class_name: 'bar-panel-container',
child: Widget.CenterBox({ child: Widget.CenterBox({
class_name: 'bar-panel', class_name: 'bar-panel',
css: 'padding: 1px', css: 'padding: 1px',
startWidget: Widget.Box({ startWidget: Widget.Box({
class_name: "box-left", class_name: 'box-left',
hexpand: true, hexpand: true,
setup: self => { setup: (self) => {
self.hook(layouts, (self) => { self.hook(layouts, (self) => {
const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout); const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.left.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>); self.children = foundLayout.left
.filter((mod) => Object.keys(widget).includes(mod))
.map((w) => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
}); });
}, },
}), }),
centerWidget: Widget.Box({ centerWidget: Widget.Box({
class_name: "box-center", class_name: 'box-center',
hpack: "center", hpack: 'center',
setup: self => { setup: (self) => {
self.hook(layouts, (self) => { self.hook(layouts, (self) => {
const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout); const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.middle.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>); self.children = foundLayout.middle
.filter((mod) => Object.keys(widget).includes(mod))
.map((w) => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
}); });
}, },
}), }),
endWidget: Widget.Box({ endWidget: Widget.Box({
class_name: "box-right", class_name: 'box-right',
hpack: "end", hpack: 'end',
setup: self => { setup: (self) => {
self.hook(layouts, (self) => { self.hook(layouts, (self) => {
const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout); const foundLayout = getLayoutForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.right.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>); self.children = foundLayout.right
.filter((mod) => Object.keys(widget).includes(mod))
.map((w) => widget[w](hyprlandMonitor) as Button<Gtk.Widget, unknown>);
}); });
}, },
}), }),

View File

@@ -1,24 +1,24 @@
import { Menu } from "./menu/index"; import { Menu } from './menu/index';
import { Workspaces } from "./workspaces/index"; import { Workspaces } from './workspaces/index';
import { ClientTitle } from "./window_title/index"; import { ClientTitle } from './window_title/index';
import { Media } from "./media/index"; import { Media } from './media/index';
import { Notifications } from "./notifications/index"; import { Notifications } from './notifications/index';
import { Volume } from "./volume/index"; import { Volume } from './volume/index';
import { Network } from "./network/index"; import { Network } from './network/index';
import { Bluetooth } from "./bluetooth/index"; import { Bluetooth } from './bluetooth/index';
import { BatteryLabel } from "./battery/index"; import { BatteryLabel } from './battery/index';
import { Clock } from "./clock/index"; import { Clock } from './clock/index';
import { SysTray } from "./systray/index"; import { SysTray } from './systray/index';
// Custom Modules // Custom Modules
import { Ram } from "../../customModules/ram/index"; import { Ram } from '../../customModules/ram/index';
import { Cpu } from "../../customModules/cpu/index"; import { Cpu } from '../../customModules/cpu/index';
import { Storage } from "customModules/storage/index"; import { Storage } from 'customModules/storage/index';
import { Netstat } from "customModules/netstat/index"; import { Netstat } from 'customModules/netstat/index';
import { KbInput } from "customModules/kblayout/index"; import { KbInput } from 'customModules/kblayout/index';
import { Updates } from "customModules/updates/index"; import { Updates } from 'customModules/updates/index';
import { Weather } from "customModules/weather/index"; import { Weather } from 'customModules/weather/index';
import { Power } from "customModules/power/index"; import { Power } from 'customModules/power/index';
export { export {
Menu, Menu,

View File

@@ -1,14 +1,14 @@
import options from "options"; import options from 'options';
const { showIcon, showTime } = options.bar.clock; const { showIcon, showTime } = options.bar.clock;
showIcon.connect("changed", () => { showIcon.connect('changed', () => {
if (!showTime.value && !showIcon.value) { if (!showTime.value && !showIcon.value) {
showTime.value = true; showTime.value = true;
} }
}); });
showTime.connect("changed", () => { showTime.connect('changed', () => {
if (!showTime.value && !showIcon.value) { if (!showTime.value && !showIcon.value) {
showIcon.value = true; showIcon.value = true;
} }

View File

@@ -1,34 +1,37 @@
const battery = await Service.import("battery"); const battery = await Service.import('battery');
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import options from "options"; import options from 'options';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { label: show_label } = options.bar.battery; const { label: show_label } = options.bar.battery;
const BatteryLabel = () => { const BatteryLabel = (): BarBoxChild => {
const isVis = Variable(battery.available); const isVis = Variable(battery.available);
const batIcon = Utils.merge([battery.bind("percent"), battery.bind("charging"), battery.bind("charged")], const batIcon = Utils.merge(
[battery.bind('percent'), battery.bind('charging'), battery.bind('charged')],
(batPercent: number, batCharging, batCharged) => { (batPercent: number, batCharging, batCharged) => {
if (batCharged) if (batCharged) return `battery-level-100-charged-symbolic`;
return `battery-level-100-charged-symbolic`; else return `battery-level-${Math.floor(batPercent / 10) * 10}${batCharging ? '-charging' : ''}-symbolic`;
else },
return `battery-level-${Math.floor(batPercent / 10) * 10}${batCharging ? '-charging' : ''}-symbolic`; );
});
battery.connect("changed", ({ available }) => { battery.connect('changed', ({ available }) => {
isVis.value = available; isVis.value = available;
}); });
const formatTime = (seconds: number) => { const formatTime = (seconds: number): Record<string, number> => {
const hours = Math.floor(seconds / 3600); const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60); const minutes = Math.floor((seconds % 3600) / 60);
return { hours, minutes }; return { hours, minutes };
}; };
const generateTooltip = (timeSeconds: number, isCharging: boolean, isCharged: boolean) => { const generateTooltip = (timeSeconds: number, isCharging: boolean, isCharged: boolean): string => {
if (isCharged) { if (isCharged) {
return "Fully Charged!!!"; return 'Fully Charged!!!';
} }
const { hours, minutes } = formatTime(timeSeconds); const { hours, minutes } = formatTime(timeSeconds);
@@ -41,60 +44,56 @@ const BatteryLabel = () => {
return { return {
component: Widget.Box({ component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), show_label.bind("value")], (style, showLabel) => { className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), show_label.bind('value')],
(style, showLabel) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `battery ${styleMap[style]} ${!showLabel ? "no-label" : ""}`; return `battery ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
}), },
visible: battery.bind("available"), ),
tooltip_text: battery.bind("time_remaining").as((t) => t.toString()), visible: battery.bind('available'),
children: Utils.merge( tooltip_text: battery.bind('time_remaining').as((t) => t.toString()),
[battery.bind("available"), show_label.bind("value")], children: Utils.merge([battery.bind('available'), show_label.bind('value')], (batAvail, showLabel) => {
(batAvail, showLabel) => {
if (batAvail && showLabel) { if (batAvail && showLabel) {
return [ return [
Widget.Icon({ Widget.Icon({
class_name: "bar-button-icon battery", class_name: 'bar-button-icon battery',
icon: batIcon icon: batIcon,
}), }),
Widget.Label({ Widget.Label({
class_name: "bar-button-label battery", class_name: 'bar-button-label battery',
label: battery.bind("percent").as((p) => `${Math.floor(p)}%`), label: battery.bind('percent').as((p) => `${Math.floor(p)}%`),
}), }),
]; ];
} else if (batAvail && !showLabel) { } else if (batAvail && !showLabel) {
return [ return [
Widget.Icon({ Widget.Icon({
class_name: "bar-button-icon battery", class_name: 'bar-button-icon battery',
icon: batIcon icon: batIcon,
}) }),
]; ];
} else { } else {
return []; return [];
} }
}, }),
),
setup: (self) => { setup: (self) => {
self.hook(battery, () => { self.hook(battery, () => {
if (battery.available) { if (battery.available) {
self.tooltip_text = generateTooltip( self.tooltip_text = generateTooltip(battery.time_remaining, battery.charging, battery.charged);
battery.time_remaining,
battery.charging,
battery.charged,
);
} }
}); });
}, },
}), }),
isVis, isVis,
boxClass: "battery", boxClass: 'battery',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "energymenu"); openMenu(clicked, event, 'energymenu');
}, },
}, },
}; };

View File

@@ -1,42 +1,41 @@
const bluetooth = await Service.import('bluetooth') const bluetooth = await Service.import('bluetooth');
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import options from "options"; import options from 'options';
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { label } = options.bar.bluetooth; const { label } = options.bar.bluetooth;
const Bluetooth = () => { const Bluetooth = (): BarBoxChild => {
const btIcon = Widget.Label({ const btIcon = Widget.Label({
label: bluetooth.bind("enabled").as((v) => v ? "󰂯" : "󰂲"), label: bluetooth.bind('enabled').as((v) => (v ? '󰂯' : '󰂲')),
class_name: "bar-button-icon bluetooth txt-icon bar", class_name: 'bar-button-icon bluetooth txt-icon bar',
}); });
const btText = Widget.Label({ const btText = Widget.Label({
label: Utils.merge([ label: Utils.merge([bluetooth.bind('enabled'), bluetooth.bind('connected_devices')], (btEnabled, btDevices) => {
bluetooth.bind("enabled"), return btEnabled && btDevices.length ? ` Connected (${btDevices.length})` : btEnabled ? 'On' : 'Off';
bluetooth.bind("connected_devices"),
],
(btEnabled, btDevices) => {
return btEnabled && btDevices.length ? ` Connected (${btDevices.length})`
: btEnabled ? "On"
: "Off"
}), }),
class_name: "bar-button-label bluetooth", class_name: 'bar-button-label bluetooth',
}); });
return { return {
component: Widget.Box({ component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), label.bind("value")], (style, showLabel) => { className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), label.bind('value')],
(style, showLabel) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `bluetooth ${styleMap[style]} ${!showLabel ? "no-label" : ""}`; return `bluetooth ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
}), },
children: options.bar.bluetooth.label.bind("value").as((showLabel) => { ),
children: options.bar.bluetooth.label.bind('value').as((showLabel) => {
if (showLabel) { if (showLabel) {
return [btIcon, btText]; return [btIcon, btText];
} }
@@ -44,14 +43,13 @@ const Bluetooth = () => {
}), }),
}), }),
isVisible: true, isVisible: true,
boxClass: "bluetooth", boxClass: 'bluetooth',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "bluetoothmenu"); openMenu(clicked, event, 'bluetoothmenu');
}, },
}, },
}; };
};
} export { Bluetooth };
export { Bluetooth }

View File

@@ -1,45 +1,46 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import GLib from "gi://GLib"; import GLib from 'gi://GLib';
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import options from "options"; import options from 'options';
import { DateTime } from 'types/@girs/glib-2.0/glib-2.0.cjs';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { format, icon, showIcon, showTime } = options.bar.clock; const { format, icon, showIcon, showTime } = options.bar.clock;
const { style } = options.theme.bar.buttons; const { style } = options.theme.bar.buttons;
const date = Variable(GLib.DateTime.new_now_local(), { const date = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()], poll: [1000, (): DateTime => GLib.DateTime.new_now_local()],
}); });
const time = Utils.derive([date, format], (c, f) => c.format(f) || ""); const time = Utils.derive([date, format], (c, f) => c.format(f) || '');
const Clock = () => {
const Clock = (): BarBoxChild => {
const clockTime = Widget.Label({ const clockTime = Widget.Label({
class_name: "bar-button-label clock bar", class_name: 'bar-button-label clock bar',
label: time.bind(), label: time.bind(),
}); });
const clockIcon = Widget.Label({ const clockIcon = Widget.Label({
label: icon.bind("value"), label: icon.bind('value'),
class_name: "bar-button-icon clock txt-icon bar", class_name: 'bar-button-icon clock txt-icon bar',
}); });
return { return {
component: Widget.Box({ component: Widget.Box({
className: Utils.merge([ className: Utils.merge(
style.bind("value"), [style.bind('value'), showIcon.bind('value'), showTime.bind('value')],
showIcon.bind("value"), showTime.bind("value") (btnStyle, shwIcn, shwLbl) => {
], (btnStyle, shwIcn, shwLbl) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `bluetooth ${styleMap[btnStyle]} ${!shwLbl ? "no-label" : ""} ${!shwIcn ? "no-icon" : ""}`; return `bluetooth ${styleMap[btnStyle]} ${!shwLbl ? 'no-label' : ''} ${!shwIcn ? 'no-icon' : ''}`;
}), },
children: Utils.merge([showIcon.bind("value"), showTime.bind("value")], (shIcn, shTm) => { ),
children: Utils.merge([showIcon.bind('value'), showTime.bind('value')], (shIcn, shTm) => {
if (shIcn && !shTm) { if (shIcn && !shTm) {
return [clockIcon]; return [clockIcon];
} else if (shTm && !shIcn) { } else if (shTm && !shIcn) {
@@ -47,13 +48,13 @@ const Clock = () => {
} }
return [clockIcon, clockTime]; return [clockIcon, clockTime];
}) }),
}), }),
isVisible: true, isVisible: true,
boxClass: "clock", boxClass: 'clock',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "calendarmenu"); openMenu(clicked, event, 'calendarmenu');
}, },
}, },
}; };

View File

@@ -1,20 +1,23 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
const mpris = await Service.import("mpris"); const mpris = await Service.import('mpris');
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import options from "options"; import options from 'options';
import { getCurrentPlayer } from 'lib/shared/media.js'; import { getCurrentPlayer } from 'lib/shared/media.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { show_artist, truncation, truncation_size, show_label, show_active_only } = options.bar.media; const { show_artist, truncation, truncation_size, show_label, show_active_only } = options.bar.media;
const Media = () => { const Media = (): BarBoxChild => {
const activePlayer = Variable(mpris.players[0]); const activePlayer = Variable(mpris.players[0]);
const isVis = Variable(!show_active_only.value); const isVis = Variable(!show_active_only.value);
show_active_only.connect("changed", () => { show_active_only.connect('changed', () => {
isVis.value = !show_active_only.value || mpris.players.length > 0; isVis.value = !show_active_only.value || mpris.players.length > 0;
}); });
mpris.connect("changed", () => { mpris.connect('changed', () => {
const curPlayer = getCurrentPlayer(activePlayer.value); const curPlayer = getCurrentPlayer(activePlayer.value);
activePlayer.value = curPlayer; activePlayer.value = curPlayer;
isVis.value = !show_active_only.value || mpris.players.length > 0; isVis.value = !show_active_only.value || mpris.players.length > 0;
@@ -22,41 +25,37 @@ const Media = () => {
const getIconForPlayer = (playerName: string): string => { const getIconForPlayer = (playerName: string): string => {
const windowTitleMap = [ const windowTitleMap = [
["Firefox", "󰈹"], ['Firefox', '󰈹'],
["Microsoft Edge", "󰇩"], ['Microsoft Edge', '󰇩'],
["Discord", ""], ['Discord', ''],
["Plex", "󰚺"], ['Plex', '󰚺'],
["Spotify", "󰓇"], ['Spotify', '󰓇'],
["(.*)", "󰝚"], ['(.*)', '󰝚'],
]; ];
const foundMatch = windowTitleMap.find((wt) => const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0], 'i').test(playerName));
RegExp(wt[0], "i").test(playerName),
);
return foundMatch ? foundMatch[1] : "󰝚"; return foundMatch ? foundMatch[1] : '󰝚';
}; };
const songIcon = Variable(""); const songIcon = Variable('');
const mediaLabel = Utils.watch("Media", [mpris, show_artist, truncation, truncation_size, show_label], () => { const mediaLabel = Utils.watch('Media', [mpris, show_artist, truncation, truncation_size, show_label], () => {
if (activePlayer.value && show_label.value) { if (activePlayer.value && show_label.value) {
const { track_title, identity, track_artists } = activePlayer.value; const { track_title, identity, track_artists } = activePlayer.value;
songIcon.value = getIconForPlayer(identity); songIcon.value = getIconForPlayer(identity);
const trackArtist = show_artist.value const trackArtist = show_artist.value ? ` - ${track_artists.join(', ')}` : ``;
? ` - ${track_artists.join(', ')}`
: ``;
const truncatedLabel = truncation.value const truncatedLabel = truncation.value
? `${track_title + trackArtist}`.substring(0, truncation_size.value) ? `${track_title + trackArtist}`.substring(0, truncation_size.value)
: `${track_title + trackArtist}`; : `${track_title + trackArtist}`;
return track_title.length === 0 return track_title.length === 0
? `No media playing...` ? `No media playing...`
: ((truncatedLabel.length < truncation_size.value) || !truncation.value) : truncatedLabel.length < truncation_size.value || !truncation.value
? `${truncatedLabel}` ? `${truncatedLabel}`
: `${truncatedLabel.substring(0, truncatedLabel.length - 3)}...`; : `${truncatedLabel.substring(0, truncatedLabel.length - 3)}...`;
} else { } else {
songIcon.value = getIconForPlayer(activePlayer.value?.identity || ""); songIcon.value = getIconForPlayer(activePlayer.value?.identity || '');
return `Media`; return `Media`;
} }
}); });
@@ -65,23 +64,26 @@ const Media = () => {
component: Widget.Box({ component: Widget.Box({
visible: false, visible: false,
child: Widget.Box({ child: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), show_label.bind("value")], (style, showLabel) => { className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), show_label.bind('value')],
(style) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `media ${styleMap[style]}`; return `media ${styleMap[style]}`;
}), },
),
child: Widget.Box({ child: Widget.Box({
children: [ children: [
Widget.Label({ Widget.Label({
class_name: "bar-button-icon media txt-icon bar", class_name: 'bar-button-icon media txt-icon bar',
label: songIcon.bind("value").as(v => v || "󰝚"), label: songIcon.bind('value').as((v) => v || '󰝚'),
}), }),
Widget.Label({ Widget.Label({
class_name: "bar-button-label media", class_name: 'bar-button-label media',
label: mediaLabel, label: mediaLabel,
}), }),
], ],
@@ -89,13 +91,13 @@ const Media = () => {
}), }),
}), }),
isVis, isVis,
boxClass: "media", boxClass: 'media',
name: "media", name: 'media',
props: { props: {
on_scroll_up: () => activePlayer.value?.next(), on_scroll_up: () => activePlayer.value?.next(),
on_scroll_down: () => activePlayer.value?.previous(), on_scroll_down: () => activePlayer.value?.previous(),
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "mediamenu"); openMenu(clicked, event, 'mediamenu');
}, },
}, },
}; };

View File

@@ -1,29 +1,32 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import options from "options"; import options from 'options';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const Menu = () => { const Menu = (): BarBoxChild => {
return { return {
component: Widget.Box({ component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value")], (style) => { className: Utils.merge([options.theme.bar.buttons.style.bind('value')], (style) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `dashboard ${styleMap[style]}`; return `dashboard ${styleMap[style]}`;
}), }),
child: Widget.Label({ child: Widget.Label({
class_name: "bar-menu_label bar-button_icon txt-icon bar", class_name: 'bar-menu_label bar-button_icon txt-icon bar',
label: options.bar.launcher.icon.bind("value"), label: options.bar.launcher.icon.bind('value'),
}), }),
}), }),
isVisible: true, isVisible: true,
boxClass: "dashboard", boxClass: 'dashboard',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "dashboardmenu"); openMenu(clicked, event, 'dashboardmenu');
}, },
}, },
}; };

View File

@@ -1,70 +1,77 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
const network = await Service.import("network"); const network = await Service.import('network');
import options from "options"; import options from 'options';
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { label: networkLabel, truncation, truncation_size } = options.bar.network; const { label: networkLabel, truncation, truncation_size } = options.bar.network;
const Network = () => { const Network = (): BarBoxChild => {
return { return {
component: Widget.Box({ component: Widget.Box({
vpack: "fill", vpack: 'fill',
vexpand: true, vexpand: true,
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), networkLabel.bind("value")], (style, showLabel) => { className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), networkLabel.bind('value')],
(style, showLabel) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `network ${styleMap[style]}${!showLabel ? " no-label" : ""}`; return `network ${styleMap[style]}${!showLabel ? ' no-label' : ''}`;
}), },
),
children: [ children: [
Widget.Icon({ Widget.Icon({
class_name: "bar-button-icon network", class_name: 'bar-button-icon network',
icon: Utils.merge([ icon: Utils.merge(
network.bind("primary"), [network.bind('primary'), network.bind('wifi'), network.bind('wired')],
network.bind("wifi"), (pmry, wfi, wrd) => {
network.bind("wired") if (pmry === 'wired') {
], (pmry, wfi, wrd) => {
if (pmry === "wired") {
return wrd.icon_name; return wrd.icon_name;
} }
return wfi.icon_name; return wfi.icon_name;
}) },
),
}), }),
Widget.Box({ Widget.Box({
vpack: "center", vpack: 'center',
child: Utils.merge([ child: Utils.merge(
network.bind("primary"), [
network.bind("wifi"), network.bind('primary'),
networkLabel.bind("value"), network.bind('wifi'),
truncation.bind("value"), networkLabel.bind('value'),
truncation_size.bind("value") truncation.bind('value'),
], (pmry, wfi, showLbl, trunc, tSize) => { truncation_size.bind('value'),
],
(pmry, wfi, showLbl, trunc, tSize) => {
if (!showLbl) { if (!showLbl) {
return Widget.Box(); return Widget.Box();
} }
if (pmry === "wired") { if (pmry === 'wired') {
return Widget.Label({ return Widget.Label({
class_name: "bar-button-label network", class_name: 'bar-button-label network',
label: "Wired".substring(0, tSize), label: 'Wired'.substring(0, tSize),
}) });
} }
return Widget.Label({ return Widget.Label({
class_name: "bar-button-label network", class_name: 'bar-button-label network',
label: wfi.ssid ? `${trunc ? wfi.ssid.substring(0, tSize) : wfi.ssid}` : "--", label: wfi.ssid ? `${trunc ? wfi.ssid.substring(0, tSize) : wfi.ssid}` : '--',
}) });
},
}) ),
}), }),
] ],
}), }),
isVisible: true, isVisible: true,
boxClass: "network", boxClass: 'network',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "networkmenu"); openMenu(clicked, event, 'networkmenu');
}, },
}, },
}; };

View File

@@ -1,51 +1,49 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import options from "options"; import options from 'options';
import { filterNotifications } from 'lib/shared/notifications.js'; import { filterNotifications } from 'lib/shared/notifications.js';
import { BarBoxChild } from 'lib/types/bar.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const { show_total } = options.bar.notifications; const { show_total } = options.bar.notifications;
const { ignore } = options.notifications; const { ignore } = options.notifications;
const notifs = await Service.import("notifications"); const notifs = await Service.import('notifications');
export const Notifications = () => { export const Notifications = (): BarBoxChild => {
return { return {
component: Widget.Box({ component: Widget.Box({
hpack: "start", hpack: 'start',
className: Utils.merge( className: Utils.merge(
[ [options.theme.bar.buttons.style.bind('value'), show_total.bind('value')],
options.theme.bar.buttons.style.bind("value"), (style, showTotal) => {
show_total.bind("value")
],
(
style,
showTotal
) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `notifications ${styleMap[style]} ${!showTotal ? "no-label" : ""}`; return `notifications ${styleMap[style]} ${!showTotal ? 'no-label' : ''}`;
}), },
),
child: Widget.Box({ child: Widget.Box({
hpack: "start", hpack: 'start',
class_name: "bar-notifications", class_name: 'bar-notifications',
children: Utils.merge( children: Utils.merge(
[notifs.bind("notifications"), notifs.bind("dnd"), show_total.bind("value"), ignore.bind("value")], [notifs.bind('notifications'), notifs.bind('dnd'), show_total.bind('value'), ignore.bind('value')],
(notif, dnd, showTotal, ignoredNotifs) => { (notif, dnd, showTotal, ignoredNotifs) => {
const filteredNotifications = filterNotifications(notif, ignoredNotifs); const filteredNotifications = filterNotifications(notif, ignoredNotifs);
const notifIcon = Widget.Label({ const notifIcon = Widget.Label({
hpack: "center", hpack: 'center',
class_name: "bar-button-icon notifications txt-icon bar", class_name: 'bar-button-icon notifications txt-icon bar',
label: dnd ? "󰂛" : filteredNotifications.length > 0 ? "󱅫" : "󰂚", label: dnd ? '󰂛' : filteredNotifications.length > 0 ? '󱅫' : '󰂚',
}); });
const notifLabel = Widget.Label({ const notifLabel = Widget.Label({
hpack: "center", hpack: 'center',
class_name: "bar-button-label notifications", class_name: 'bar-button-label notifications',
label: filteredNotifications.length.toString(), label: filteredNotifications.length.toString(),
}); });
@@ -58,10 +56,10 @@ export const Notifications = () => {
}), }),
}), }),
isVisible: true, isVisible: true,
boxClass: "notifications", boxClass: 'notifications',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "notificationsmenu"); openMenu(clicked, event, 'notificationsmenu');
}, },
}, },
}; };

View File

@@ -1,49 +1,43 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import { BarBoxChild, SelfButton } from 'lib/types/bar';
import { Notify } from 'lib/utils'; import { Notify } from 'lib/utils';
const systemtray = await Service.import("systemtray"); const systemtray = await Service.import('systemtray');
import options from "options"; import options from 'options';
const { ignore } = options.bar.systray; const { ignore } = options.bar.systray;
const SysTray = () => { const SysTray = (): BarBoxChild => {
const isVis = Variable(false); const isVis = Variable(false);
const items = Utils.merge( const items = Utils.merge([systemtray.bind('items'), ignore.bind('value')], (items, ignored) => {
[systemtray.bind("items"), ignore.bind("value")],
(items, ignored) => {
const filteredTray = items.filter(({ id }) => !ignored.includes(id)); const filteredTray = items.filter(({ id }) => !ignored.includes(id));
isVis.value = filteredTray.length > 0; isVis.value = filteredTray.length > 0;
return filteredTray.map((item) => { return filteredTray.map((item) => {
if (item.menu !== undefined) {
item.menu["class_name"] = "systray-menu";
}
return Widget.Button({ return Widget.Button({
cursor: "pointer", cursor: 'pointer',
child: Widget.Icon({ child: Widget.Icon({
class_name: "systray-icon", class_name: 'systray-icon',
icon: item.bind("icon"), icon: item.bind('icon'),
}), }),
on_primary_click: (_: any, event: Gdk.Event) => item.activate(event), on_primary_click: (_: SelfButton, event: Gdk.Event) => item.activate(event),
on_secondary_click: (_, event) => item.openMenu(event), on_secondary_click: (_, event) => item.openMenu(event),
onMiddleClick: () => Notify({ summary: "App Name", body: item.id }), onMiddleClick: () => Notify({ summary: 'App Name', body: item.id }),
tooltip_markup: item.bind("tooltip_markup"), tooltip_markup: item.bind('tooltip_markup'),
});
}); });
}); });
},
);
return { return {
component: Widget.Box({ component: Widget.Box({
class_name: "systray", class_name: 'systray',
children: items, children: items,
}), }),
isVisible: true, isVisible: true,
boxClass: "systray", boxClass: 'systray',
isVis, isVis,
props: {} props: {},
}; };
}; };

View File

@@ -1,6 +1,8 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
import { Child } from 'lib/types/widget';
import Button from 'types/widgets/button';
export const closeAllMenus = () => { export const closeAllMenus = (): void => {
const menuWindows = App.windows const menuWindows = App.windows
.filter((w) => { .filter((w) => {
if (w.name) { if (w.name) {
@@ -18,7 +20,7 @@ export const closeAllMenus = () => {
}); });
}; };
export const openMenu = (clicked: any, event: Gdk.Event, window: string) => { export const openMenu = (clicked: Button<Child, Child>, event: Gdk.Event, window: string): void => {
/* /*
* NOTE: We have to make some adjustments so the menu pops up relatively * NOTE: We have to make some adjustments so the menu pops up relatively
* to the center of the button clicked. We don't want the menu to spawn * to the center of the button clicked. We don't want the menu to spawn

View File

@@ -1,59 +1,63 @@
import Gdk from 'gi://Gdk?version=3.0'; import Gdk from 'gi://Gdk?version=3.0';
const audio = await Service.import("audio"); const audio = await Service.import('audio');
import { openMenu } from "../utils.js"; import { openMenu } from '../utils.js';
import options from "options"; import options from 'options';
import { Binding } from 'lib/utils.js'; import { Binding } from 'lib/utils.js';
import { VolumeIcons } from 'lib/types/volume.js'; import { VolumeIcons } from 'lib/types/volume.js';
import { BarBoxChild } from 'lib/types/bar.js';
import { Bind } from 'lib/types/variable.js';
import Button from 'types/widgets/button.js';
import { Child } from 'lib/types/widget.js';
const Volume = () => { const Volume = (): BarBoxChild => {
const icons: VolumeIcons = { const icons: VolumeIcons = {
101: "󰕾", 101: '󰕾',
66: "󰕾", 66: '󰕾',
34: "󰖀", 34: '󰖀',
1: "󰕿", 1: '󰕿',
0: "󰝟", 0: '󰝟',
}; };
const getIcon = () => { const getIcon = (): Bind => {
const icon: Binding<number> = Utils.merge( const icon: Binding<number> = Utils.merge(
[audio.speaker.bind("is_muted"), audio.speaker.bind("volume")], [audio.speaker.bind('is_muted'), audio.speaker.bind('volume')],
(isMuted, vol) => { (isMuted, vol) => {
return isMuted return isMuted ? 0 : [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100) || 101;
? 0
: [101, 66, 34, 1, 0].find((threshold) => threshold <= vol * 100) || 101;
}, },
); );
return icon.as((i: number) => i !== undefined ? icons[i] : icons[101]); return icon.as((i: number) => (i !== undefined ? icons[i] : icons[101]));
}; };
const volIcn = Widget.Label({ const volIcn = Widget.Label({
hexpand: true, hexpand: true,
label: getIcon(), label: getIcon(),
class_name: "bar-button-icon volume txt-icon bar", class_name: 'bar-button-icon volume txt-icon bar',
}); });
const volPct = Widget.Label({ const volPct = Widget.Label({
hexpand: true, hexpand: true,
label: audio.speaker.bind("volume").as((v) => `${Math.round(v * 100)}%`), label: audio.speaker.bind('volume').as((v) => `${Math.round(v * 100)}%`),
class_name: "bar-button-label volume", class_name: 'bar-button-label volume',
}); });
return { return {
component: Widget.Box({ component: Widget.Box({
hexpand: true, hexpand: true,
vexpand: true, vexpand: true,
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), options.bar.volume.label.bind("value")], (style, showLabel) => { className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), options.bar.volume.label.bind('value')],
(style, showLabel) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `volume ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
return `volume ${styleMap[style]} ${!showLabel ? "no-label" : ""}`; },
}), ),
children: options.bar.volume.label.bind("value").as((showLabel) => { children: options.bar.volume.label.bind('value').as((showLabel) => {
if (showLabel) { if (showLabel) {
return [volIcn, volPct]; return [volIcn, volPct];
} }
@@ -61,10 +65,10 @@ const Volume = () => {
}), }),
}), }),
isVisible: true, isVisible: true,
boxClass: "volume", boxClass: 'volume',
props: { props: {
on_primary_click: (clicked: any, event: Gdk.Event) => { on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, "audiomenu"); openMenu(clicked, event, 'audiomenu');
}, },
}, },
}; };

View File

@@ -1,122 +1,119 @@
const hyprland = await Service.import("hyprland"); const hyprland = await Service.import('hyprland');
import { BarBoxChild } from 'lib/types/bar';
import options from 'options'; import options from 'options';
import { ActiveClient } from 'types/service/hyprland' import { ActiveClient } from 'types/service/hyprland';
import Label from "types/widgets/label";
const filterTitle = (windowtitle: ActiveClient) => { const filterTitle = (windowtitle: ActiveClient): Record<string, string> => {
const windowTitleMap = [ const windowTitleMap = [
// user provided values // user provided values
...options.bar.windowtitle.title_map.value, ...options.bar.windowtitle.title_map.value,
// Original Entries // Original Entries
["kitty", "󰄛", "Kitty Terminal"], ['kitty', '󰄛', 'Kitty Terminal'],
["firefox", "󰈹", "Firefox"], ['firefox', '󰈹', 'Firefox'],
["microsoft-edge", "󰇩", "Edge"], ['microsoft-edge', '󰇩', 'Edge'],
["discord", "", "Discord"], ['discord', '', 'Discord'],
["vesktop", "", "Vesktop"], ['vesktop', '', 'Vesktop'],
["org.kde.dolphin", "", "Dolphin"], ['org.kde.dolphin', '', 'Dolphin'],
["plex", "󰚺", "Plex"], ['plex', '󰚺', 'Plex'],
["steam", "", "Steam"], ['steam', '', 'Steam'],
["spotify", "󰓇", "Spotify"], ['spotify', '󰓇', 'Spotify'],
["ristretto", "󰋩", "Ristretto"], ['ristretto', '󰋩', 'Ristretto'],
["obsidian", "󱓧", "Obsidian"], ['obsidian', '󱓧', 'Obsidian'],
// Browsers // Browsers
["google-chrome", "", "Google Chrome"], ['google-chrome', '', 'Google Chrome'],
["brave-browser", "󰖟", "Brave Browser"], ['brave-browser', '󰖟', 'Brave Browser'],
["chromium", "", "Chromium"], ['chromium', '', 'Chromium'],
["opera", "", "Opera"], ['opera', '', 'Opera'],
["vivaldi", "󰖟", "Vivaldi"], ['vivaldi', '󰖟', 'Vivaldi'],
["waterfox", "󰖟", "Waterfox"], ['waterfox', '󰖟', 'Waterfox'],
["thorium", "󰖟", "Waterfox"], ['thorium', '󰖟', 'Waterfox'],
["tor-browser", "", "Tor Browser"], ['tor-browser', '', 'Tor Browser'],
["floorp", "󰈹", "Floorp"], ['floorp', '󰈹', 'Floorp'],
// Terminals // Terminals
["gnome-terminal", "", "GNOME Terminal"], ['gnome-terminal', '', 'GNOME Terminal'],
["konsole", "", "Konsole"], ['konsole', '', 'Konsole'],
["alacritty", "", "Alacritty"], ['alacritty', '', 'Alacritty'],
["wezterm", "", "Wezterm"], ['wezterm', '', 'Wezterm'],
["foot", "󰽒", "Foot Terminal"], ['foot', '󰽒', 'Foot Terminal'],
["tilix", "", "Tilix"], ['tilix', '', 'Tilix'],
["xterm", "", "XTerm"], ['xterm', '', 'XTerm'],
["urxvt", "", "URxvt"], ['urxvt', '', 'URxvt'],
["st", "", "st Terminal"], ['st', '', 'st Terminal'],
// Development Tools // Development Tools
["code", "󰨞", "Visual Studio Code"], ['code', '󰨞', 'Visual Studio Code'],
["vscode", "󰨞", "VS Code"], ['vscode', '󰨞', 'VS Code'],
["sublime-text", "", "Sublime Text"], ['sublime-text', '', 'Sublime Text'],
["atom", "", "Atom"], ['atom', '', 'Atom'],
["android-studio", "󰀴", "Android Studio"], ['android-studio', '󰀴', 'Android Studio'],
["intellij-idea", "", "IntelliJ IDEA"], ['intellij-idea', '', 'IntelliJ IDEA'],
["pycharm", "󱃖", "PyCharm"], ['pycharm', '󱃖', 'PyCharm'],
["webstorm", "󱃖", "WebStorm"], ['webstorm', '󱃖', 'WebStorm'],
["phpstorm", "󱃖", "PhpStorm"], ['phpstorm', '󱃖', 'PhpStorm'],
["eclipse", "", "Eclipse"], ['eclipse', '', 'Eclipse'],
["netbeans", "", "NetBeans"], ['netbeans', '', 'NetBeans'],
["docker", "", "Docker"], ['docker', '', 'Docker'],
["vim", "", "Vim"], ['vim', '', 'Vim'],
["neovim", "", "Neovim"], ['neovim', '', 'Neovim'],
["neovide", "", "Neovide"], ['neovide', '', 'Neovide'],
["emacs", "", "Emacs"], ['emacs', '', 'Emacs'],
// Communication Tools // Communication Tools
["slack", "󰒱", "Slack"], ['slack', '󰒱', 'Slack'],
["telegram-desktop", "", "Telegram"], ['telegram-desktop', '', 'Telegram'],
["org.telegram.desktop", "", "Telegram"], ['org.telegram.desktop', '', 'Telegram'],
["whatsapp", "󰖣", "WhatsApp"], ['whatsapp', '󰖣', 'WhatsApp'],
["teams", "󰊻", "Microsoft Teams"], ['teams', '󰊻', 'Microsoft Teams'],
["skype", "󰒯", "Skype"], ['skype', '󰒯', 'Skype'],
["thunderbird", "", "Thunderbird"], ['thunderbird', '', 'Thunderbird'],
// File Managers // File Managers
["nautilus", "󰝰", "Files (Nautilus)"], ['nautilus', '󰝰', 'Files (Nautilus)'],
["thunar", "󰝰", "Thunar"], ['thunar', '󰝰', 'Thunar'],
["pcmanfm", "󰝰", "PCManFM"], ['pcmanfm', '󰝰', 'PCManFM'],
["nemo", "󰝰", "Nemo"], ['nemo', '󰝰', 'Nemo'],
["ranger", "󰝰", "Ranger"], ['ranger', '󰝰', 'Ranger'],
["doublecmd", "󰝰", "Double Commander"], ['doublecmd', '󰝰', 'Double Commander'],
["krusader", "󰝰", "Krusader"], ['krusader', '󰝰', 'Krusader'],
// Media Players // Media Players
["vlc", "󰕼", "VLC Media Player"], ['vlc', '󰕼', 'VLC Media Player'],
["mpv", "", "MPV"], ['mpv', '', 'MPV'],
["rhythmbox", "󰓃", "Rhythmbox"], ['rhythmbox', '󰓃', 'Rhythmbox'],
// Graphics Tools // Graphics Tools
["gimp", "", "GIMP"], ['gimp', '', 'GIMP'],
["inkscape", "", "Inkscape"], ['inkscape', '', 'Inkscape'],
["krita", "", "Krita"], ['krita', '', 'Krita'],
["blender", "󰂫", "Blender"], ['blender', '󰂫', 'Blender'],
// Video Editing // Video Editing
["kdenlive", "", "Kdenlive"], ['kdenlive', '', 'Kdenlive'],
// Games and Gaming Platforms // Games and Gaming Platforms
["lutris", "󰺵", "Lutris"], ['lutris', '󰺵', 'Lutris'],
["heroic", "󰺵", "Heroic Games Launcher"], ['heroic', '󰺵', 'Heroic Games Launcher'],
["minecraft", "󰍳", "Minecraft"], ['minecraft', '󰍳', 'Minecraft'],
["csgo", "󰺵", "CS:GO"], ['csgo', '󰺵', 'CS:GO'],
["dota2", "󰺵", "Dota 2"], ['dota2', '󰺵', 'Dota 2'],
// Office and Productivity // Office and Productivity
["evernote", "", "Evernote"], ['evernote', '', 'Evernote'],
["sioyek", "", "Sioyek"], ['sioyek', '', 'Sioyek'],
// Cloud Services and Sync // Cloud Services and Sync
["dropbox", "󰇣", "Dropbox"], ['dropbox', '󰇣', 'Dropbox'],
// Desktop // Desktop
["^$", "󰇄", "Desktop"], ['^$', '󰇄', 'Desktop'],
// Fallback icon // Fallback icon
["(.+)", "󰣆", `${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`], ['(.+)', '󰣆', `${windowtitle.class.charAt(0).toUpperCase() + windowtitle.class.slice(1)}`],
]; ];
const foundMatch = windowTitleMap.find((wt) => const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0]).test(windowtitle.class.toLowerCase()));
RegExp(wt[0]).test(windowtitle.class.toLowerCase()),
);
// return the default icon if no match is found or // return the default icon if no match is found or
// if the array element matched is not of size 3 // if the array element matched is not of size 3
@@ -129,15 +126,15 @@ const filterTitle = (windowtitle: ActiveClient) => {
return { return {
icon: foundMatch[1], icon: foundMatch[1],
label: foundMatch[2] label: foundMatch[2],
}; };
}; };
const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: boolean) => { const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: boolean): string => {
if (useCustomTitle) return filterTitle(client).label; if (useCustomTitle) return filterTitle(client).label;
if (useClassName) return client.class; if (useClassName) return client.class;
let title = client.title; const title = client.title;
// If the title is empty or only filled with spaces, fallback to the class name // If the title is empty or only filled with spaces, fallback to the class name
if (title.length === 0 || title.match(/^ *$/)) { if (title.length === 0 || title.match(/^ *$/)) {
return client.class; return client.class;
@@ -145,51 +142,76 @@ const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: b
return title; return title;
}; };
const truncateTitle = (title: string, max_size: number) => { const truncateTitle = (title: string, max_size: number): string => {
if (max_size > 0 && title.length > max_size) { if (max_size > 0 && title.length > max_size) {
return title.substring(0, max_size).trim() + "..."; return title.substring(0, max_size).trim() + '...';
} }
return title; return title;
}; };
const ClientTitle = () => { const ClientTitle = (): BarBoxChild => {
const { custom_title, class_name, label, icon, truncation, truncation_size } = options.bar.windowtitle; const { custom_title, class_name, label, icon, truncation, truncation_size } = options.bar.windowtitle;
return { return {
component: Widget.Box({ component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), label.bind("value")], (style, showLabel) => { className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), label.bind('value')],
(style, showLabel) => {
const styleMap = { const styleMap = {
default: "style1", default: 'style1',
split: "style2", split: 'style2',
wave: "style3", wave: 'style3',
wave2: "style3", wave2: 'style3',
}; };
return `windowtitle ${styleMap[style]} ${!showLabel ? "no-label" : ""}`; return `windowtitle ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
}), },
children: ),
Utils.merge( children: Utils.merge(
[hyprland.active.bind("client"), custom_title.bind("value"), class_name.bind("value"), label.bind("value"), [
icon.bind("value"), truncation.bind("value"), truncation_size.bind("value")], hyprland.active.bind('client'),
custom_title.bind('value'),
class_name.bind('value'),
label.bind('value'),
icon.bind('value'),
truncation.bind('value'),
truncation_size.bind('value'),
],
(client, useCustomTitle, useClassName, showLabel, showIcon, truncate, truncationSize) => { (client, useCustomTitle, useClassName, showLabel, showIcon, truncate, truncationSize) => {
const children: Label<any>[] = [];
if (showIcon) { if (showIcon) {
children.push(Widget.Label({ return [
class_name: "bar-button-icon windowtitle txt-icon bar", Widget.Label({
class_name: 'bar-button-icon windowtitle txt-icon bar',
label: filterTitle(client).icon, label: filterTitle(client).icon,
}));
}
if (showLabel) {
children.push(Widget.Label({
class_name: `bar-button-label windowtitle ${showIcon ? "" : "no-icon"}`,
label: truncateTitle(getTitle(client, useCustomTitle, useClassName), truncate ? truncationSize : -1),
}));
}
return children;
}), }),
Widget.Label({
class_name: `bar-button-label windowtitle ${showIcon ? '' : 'no-icon'}`,
label: truncateTitle(
getTitle(client, useCustomTitle, useClassName),
truncate ? truncationSize : -1,
),
}),
];
}
if (showLabel) {
return [
Widget.Label({
class_name: `bar-button-label windowtitle ${showIcon ? '' : 'no-icon'}`,
label: truncateTitle(
getTitle(client, useCustomTitle, useClassName),
truncate ? truncationSize : -1,
),
}),
];
}
return [];
},
),
}), }),
isVisible: true, isVisible: true,
boxClass: "windowtitle", boxClass: 'windowtitle',
props: {} props: {},
}; };
}; };

View File

@@ -1,23 +1,21 @@
const hyprland = await Service.import("hyprland"); const hyprland = await Service.import('hyprland');
import { WorkspaceMap, WorkspaceRule } from "lib/types/workspace"; import { MonitorMap, WorkspaceMap, WorkspaceRule } from 'lib/types/workspace';
import options from "options"; import options from 'options';
import { Variable } from "types/variable"; import { Variable } from 'types/variable';
const {
workspaces,
reverse_scroll,
} = options.bar.workspaces;
const { workspaces, reverse_scroll } = options.bar.workspaces;
export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, monitor: number): boolean => { export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, monitor: number): boolean => {
if (!wsRules || !Object.keys(wsRules).length) { if (!wsRules || !Object.keys(wsRules).length) {
return true; return true;
} }
const monitorMap = {}; const monitorMap: MonitorMap = {};
const workspaceMonitorList = hyprland?.workspaces?.map(m => ({ id: m.monitorID, name: m.monitor })); const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ id: m.monitorID, name: m.monitor }));
const monitors = [...new Map([...workspaceMonitorList, ...hyprland.monitors].map(item => [item.id, item])).values()]; const monitors = [
...new Map([...workspaceMonitorList, ...hyprland.monitors].map((item) => [item.id, item])).values(),
];
monitors.forEach((m) => (monitorMap[m.id] = m.name)); monitors.forEach((m) => (monitorMap[m.id] = m.name));
@@ -32,9 +30,9 @@ export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, mo
export const getWorkspaceRules = (): WorkspaceMap => { export const getWorkspaceRules = (): WorkspaceMap => {
try { try {
const rules = Utils.exec("hyprctl workspacerules -j"); const rules = Utils.exec('hyprctl workspacerules -j');
const workspaceRules = {}; const workspaceRules: WorkspaceMap = {};
JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => { JSON.parse(rules).forEach((rule: WorkspaceRule, index: number) => {
if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) { if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) {
@@ -60,13 +58,13 @@ export const getCurrentMonitorWorkspaces = (monitor: number): number[] => {
} }
const monitorWorkspaces = getWorkspaceRules(); const monitorWorkspaces = getWorkspaceRules();
const monitorMap = {}; const monitorMap: MonitorMap = {};
hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name)); hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name));
const currentMonitorName = monitorMap[monitor]; const currentMonitorName = monitorMap[monitor];
return monitorWorkspaces[currentMonitorName]; return monitorWorkspaces[currentMonitorName];
} };
export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => { export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => {
if (activeWorkspaces === true) { if (activeWorkspaces === true) {
@@ -74,18 +72,17 @@ export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
let nextIndex = hyprland.active.workspace.id + 1; let nextIndex = hyprland.active.workspace.id + 1;
if (nextIndex > activeWses[activeWses.length - 1].id) { if (nextIndex > activeWses[activeWses.length - 1].id) {
nextIndex = activeWses[0].id; nextIndex = activeWses[0].id;
} }
hyprland.messageAsync(`dispatch workspace ${nextIndex}`) hyprland.messageAsync(`dispatch workspace ${nextIndex}`);
} else if (currentMonitorWorkspaces.value === undefined) { } else if (currentMonitorWorkspaces.value === undefined) {
let nextIndex = hyprland.active.workspace.id + 1; let nextIndex = hyprland.active.workspace.id + 1;
if (nextIndex > workspaces.value) { if (nextIndex > workspaces.value) {
nextIndex = 0; nextIndex = 0;
} }
hyprland.messageAsync(`dispatch workspace ${nextIndex}`) hyprland.messageAsync(`dispatch workspace ${nextIndex}`);
} else { } else {
const curWorkspace = hyprland.active.workspace.id; const curWorkspace = hyprland.active.workspace.id;
const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace); const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace);
@@ -94,9 +91,9 @@ export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
nextIndex = 0; nextIndex = 0;
} }
hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[nextIndex]}`) hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[nextIndex]}`);
} }
} };
export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => { export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => {
if (activeWorkspaces === true) { if (activeWorkspaces === true) {
@@ -104,11 +101,10 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
let prevIndex = hyprland.active.workspace.id - 1; let prevIndex = hyprland.active.workspace.id - 1;
if (prevIndex < activeWses[0].id) { if (prevIndex < activeWses[0].id) {
prevIndex = activeWses[activeWses.length - 1].id; prevIndex = activeWses[activeWses.length - 1].id;
} }
hyprland.messageAsync(`dispatch workspace ${prevIndex}`) hyprland.messageAsync(`dispatch workspace ${prevIndex}`);
} else if (currentMonitorWorkspaces.value === undefined) { } else if (currentMonitorWorkspaces.value === undefined) {
let prevIndex = hyprland.active.workspace.id - 1; let prevIndex = hyprland.active.workspace.id - 1;
@@ -116,7 +112,7 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
prevIndex = workspaces.value; prevIndex = workspaces.value;
} }
hyprland.messageAsync(`dispatch workspace ${prevIndex}`) hyprland.messageAsync(`dispatch workspace ${prevIndex}`);
} else { } else {
const curWorkspace = hyprland.active.workspace.id; const curWorkspace = hyprland.active.workspace.id;
const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace); const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace);
@@ -125,11 +121,11 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
prevIndex = currentMonitorWorkspaces.value.length - 1; prevIndex = currentMonitorWorkspaces.value.length - 1;
} }
hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[prevIndex]}`) hyprland.messageAsync(`dispatch workspace ${currentMonitorWorkspaces.value[prevIndex]}`);
} }
} };
export function throttle<T extends (...args: any[]) => void>(func: T, limit: number): T { export function throttle<T extends (...args: unknown[]) => void>(func: T, limit: number): T {
let inThrottle: boolean; let inThrottle: boolean;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) { return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) { if (!inThrottle) {
@@ -147,7 +143,11 @@ type ThrottledScrollHandlers = {
throttledScrollDown: () => void; throttledScrollDown: () => void;
}; };
export const createThrottledScrollHandlers = (scrollSpeed: number, currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean = false): ThrottledScrollHandlers => { export const createThrottledScrollHandlers = (
scrollSpeed: number,
currentMonitorWorkspaces: Variable<number[]>,
activeWorkspaces: boolean = false,
): ThrottledScrollHandlers => {
const throttledScrollUp = throttle(() => { const throttledScrollUp = throttle(() => {
if (reverse_scroll.value === true) { if (reverse_scroll.value === true) {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces); goToPrevWS(currentMonitorWorkspaces, activeWorkspaces);
@@ -165,4 +165,4 @@ export const createThrottledScrollHandlers = (scrollSpeed: number, currentMonito
}, 200 / scrollSpeed); }, 200 / scrollSpeed);
return { throttledScrollUp, throttledScrollDown }; return { throttledScrollUp, throttledScrollDown };
} };

View File

@@ -1,42 +1,54 @@
const hyprland = await Service.import("hyprland"); const hyprland = await Service.import('hyprland');
import options from "options"; import options from 'options';
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces, getWorkspaceRules, getWorkspacesForMonitor } from "./helpers"; import {
import { Workspace } from "types/service/hyprland"; createThrottledScrollHandlers,
getCurrentMonitorWorkspaces,
getWorkspaceRules,
getWorkspacesForMonitor,
} from './helpers';
import { Workspace } from 'types/service/hyprland';
import { BoxWidget } from 'lib/types/widget';
import { BarBoxChild, SelfButton } from 'lib/types/bar';
const { const { workspaces, monitorSpecific, workspaceMask, scroll_speed, spacing } = options.bar.workspaces;
workspaces,
monitorSpecific,
workspaceMask,
scroll_speed,
spacing
} = options.bar.workspaces;
function range(length: number, start = 1) { function range(length: number, start = 1): number[] {
return Array.from({ length }, (_, i) => i + start); return Array.from({ length }, (_, i) => i + start);
} }
const Workspaces = (monitor = -1) => { const Workspaces = (monitor = -1): BarBoxChild => {
const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor)); const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor));
workspaces.connect("changed", () => { workspaces.connect('changed', () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor) currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor);
}) });
const renderClassnames = (showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string, i: number) => { const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
i: number,
): string => {
if (showIcons) { if (showIcons) {
return `workspace-icon txt-icon bar`; return `workspace-icon txt-icon bar`;
} }
if (showNumbered) { if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
? numberedActiveIndicator
: "";
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
} }
return "default"; return 'default';
} };
const renderLabel = (showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, i: number, index: number) => { const renderLabel = (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
i: number,
index: number,
): string => {
if (showIcons) { if (showIcons) {
if (hyprland.active.workspace.id === i) { if (hyprland.active.workspace.id === i) {
return active; return active;
@@ -44,20 +56,16 @@ const Workspaces = (monitor = -1) => {
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) { if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied; return occupied;
} }
if ( if (monitor !== -1) {
monitor !== -1
) {
return available; return available;
} }
} }
return workspaceMask return workspaceMask ? `${index + 1}` : `${i}`;
? `${index + 1}` };
: `${i}`; const defaultWses = (): BoxWidget => {
}
const defaultWses = () => {
return Widget.Box({ return Widget.Box({
children: Utils.merge( children: Utils.merge(
[workspaces.bind("value"), monitorSpecific.bind()], [workspaces.bind('value'), monitorSpecific.bind()],
(workspaces: number, monitorSpecific: boolean) => { (workspaces: number, monitorSpecific: boolean) => {
return range(workspaces || 8) return range(workspaces || 8)
.filter((i) => { .filter((i) => {
@@ -72,49 +80,57 @@ const Workspaces = (monitor = -1) => {
}) })
.map((i, index) => { .map((i, index) => {
return Widget.Button({ return Widget.Button({
class_name: "workspace-button", class_name: 'workspace-button',
on_primary_click: () => { on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`) hyprland.messageAsync(`dispatch workspace ${i}`);
}, },
child: Widget.Label({ child: Widget.Label({
attribute: i, attribute: i,
vpack: "center", vpack: 'center',
css: spacing.bind("value").as(sp => `margin: 0rem ${0.375 * sp}rem;`), css: spacing.bind('value').as((sp) => `margin: 0rem ${0.375 * sp}rem;`),
class_name: Utils.merge( class_name: Utils.merge(
[ [
options.bar.workspaces.show_icons.bind("value"), options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.show_numbered.bind("value"), options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind("value"), options.bar.workspaces.numbered_active_indicator.bind('value'),
options.bar.workspaces.icons.available.bind("value"), options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind("value"), options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind("value"), options.bar.workspaces.icons.occupied.bind('value'),
hyprland.active.workspace.bind("id") hyprland.active.workspace.bind('id'),
], ],
(showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string) => { (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
) => {
if (showIcons) { if (showIcons) {
return `workspace-icon txt-icon bar`; return `workspace-icon txt-icon bar`;
} }
if (showNumbered) { if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i const numActiveInd =
? numberedActiveIndicator hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
: "";
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`; return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
} }
return "default"; return 'default';
}, },
), ),
label: Utils.merge( label: Utils.merge(
[ [
options.bar.workspaces.show_icons.bind("value"), options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind("value"), options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind("value"), options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind("value"), options.bar.workspaces.icons.occupied.bind('value'),
workspaceMask.bind("value"), workspaceMask.bind('value'),
hyprland.active.workspace.bind("id") hyprland.active.workspace.bind('id'),
], ],
(showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, _: number) => { (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
) => {
if (showIcons) { if (showIcons) {
if (hyprland.active.workspace.id === i) { if (hyprland.active.workspace.id === i) {
return active; return active;
@@ -122,52 +138,45 @@ const Workspaces = (monitor = -1) => {
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) { if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied; return occupied;
} }
if ( if (monitor !== -1) {
monitor !== -1
) {
return available; return available;
} }
} }
return workspaceMask return workspaceMask ? `${index + 1}` : `${i}`;
? `${index + 1}`
: `${i}`;
}, },
), ),
setup: (self) => { setup: (self) => {
self.hook(hyprland, () => { self.hook(hyprland, () => {
self.toggleClassName('active', hyprland.active.workspace.id === i);
self.toggleClassName( self.toggleClassName(
"active", 'occupied',
hyprland.active.workspace.id === i,
);
self.toggleClassName(
"occupied",
(hyprland.getWorkspace(i)?.windows || 0) > 0, (hyprland.getWorkspace(i)?.windows || 0) > 0,
); );
}); });
}, },
}) }),
}); });
}); });
}, },
) ),
}) });
} };
const occupiedWses = () => { const occupiedWses = (): BoxWidget => {
return Widget.Box({ return Widget.Box({
children: Utils.merge( children: Utils.merge(
[ [
monitorSpecific.bind("value"), monitorSpecific.bind('value'),
hyprland.bind("workspaces"), hyprland.bind('workspaces'),
workspaceMask.bind("value"), workspaceMask.bind('value'),
workspaces.bind("value"), workspaces.bind('value'),
options.bar.workspaces.show_icons.bind("value"), options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind("value"), options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind("value"), options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind("value"), options.bar.workspaces.icons.occupied.bind('value'),
options.bar.workspaces.show_numbered.bind("value"), options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind("value"), options.bar.workspaces.numbered_active_indicator.bind('value'),
spacing.bind("value"), spacing.bind('value'),
hyprland.active.workspace.bind("id"), hyprland.active.workspace.bind('id'),
], ],
( (
monitorSpecific: boolean, monitorSpecific: boolean,
@@ -185,29 +194,37 @@ const Workspaces = (monitor = -1) => {
) => { ) => {
let allWkspcs = range(totalWkspcs || 8); let allWkspcs = range(totalWkspcs || 8);
const activeWorkspaces = wkSpaces.map(w => w.id); const activeWorkspaces = wkSpaces.map((w) => w.id);
const workspaceRules = getWorkspaceRules(); const workspaceRules = getWorkspaceRules();
// Sometimes hyprland doesn't have all the monitors in the list // Sometimes hyprland doesn't have all the monitors in the list
// so we complement it with monitors from the workspace list // so we complement it with monitors from the workspace list
const workspaceMonitorList = hyprland?.workspaces?.map(m => ({ id: m.monitorID, name: m.monitor })); const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({
const curMonitor = hyprland.monitors.find(m => m.id === monitor) id: m.monitorID,
|| workspaceMonitorList.find(m => m.id === monitor); name: m.monitor,
}));
const curMonitor =
hyprland.monitors.find((m) => m.id === monitor) ||
workspaceMonitorList.find((m) => m.id === monitor);
// go through each key in workspaceRules and flatten the array // go through each key in workspaceRules and flatten the array
const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => { const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => {
return [...acc, ...workspaceRules[k]]; return [...acc, ...workspaceRules[k]];
}, [] as number[]); }, [] as number[]);
const activesForMonitor = activeWorkspaces.filter(w => { const activesForMonitor = activeWorkspaces.filter((w) => {
if (curMonitor && Object.hasOwnProperty.call(workspaceRules, curMonitor.name) && workspacesWithRules.includes(w)) { if (
curMonitor &&
Object.hasOwnProperty.call(workspaceRules, curMonitor.name) &&
workspacesWithRules.includes(w)
) {
return workspaceRules[curMonitor.name].includes(w); return workspaceRules[curMonitor.name].includes(w);
} }
return true; return true;
}); });
if (monitorSpecific) { if (monitorSpecific) {
const wrkspcsInRange = range(totalWkspcs).filter(w => { const wrkspcsInRange = range(totalWkspcs).filter((w) => {
return getWorkspacesForMonitor(w, workspaceRules, monitor); return getWorkspacesForMonitor(w, workspaceRules, monitor);
}); });
allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])]; allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])];
@@ -221,51 +238,53 @@ const Workspaces = (monitor = -1) => {
}) })
.map((i, index) => { .map((i, index) => {
return Widget.Button({ return Widget.Button({
class_name: "workspace-button", class_name: 'workspace-button',
on_primary_click: () => { on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`) hyprland.messageAsync(`dispatch workspace ${i}`);
}, },
child: Widget.Label({ child: Widget.Label({
attribute: i, attribute: i,
vpack: "center", vpack: 'center',
css: `margin: 0rem ${0.375 * spacing}rem;`, css: `margin: 0rem ${0.375 * spacing}rem;`,
class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i), class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i),
label: renderLabel(showIcons, available, active, occupied, workspaceMask, i, index), label: renderLabel(showIcons, available, active, occupied, workspaceMask, i, index),
setup: (self) => { setup: (self) => {
self.toggleClassName( self.toggleClassName('active', activeId === i);
"active", self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
activeId === i,
);
self.toggleClassName(
"occupied",
(hyprland.getWorkspace(i)?.windows || 0) > 0,
);
}, },
}) }),
}); });
}); });
}, },
) ),
}) });
} };
return { return {
component: Widget.Box({ component: Widget.Box({
class_name: "workspaces", class_name: 'workspaces',
child: options.bar.workspaces.hideUnoccupied.bind("value").as(hideUnoccupied => hideUnoccupied ? occupiedWses() : defaultWses()), child: options.bar.workspaces.hideUnoccupied
.bind('value')
.as((hideUnoccupied) => (hideUnoccupied ? occupiedWses() : defaultWses())),
}), }),
isVisible: true, isVisible: true,
boxClass: "workspaces", boxClass: 'workspaces',
props: { props: {
setup: (self: any) => { setup: (self: SelfButton): void => {
Utils.merge([scroll_speed.bind("value"), options.bar.workspaces.hideUnoccupied.bind("value")], (scroll_speed, hideUnoccupied) => { Utils.merge(
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(scroll_speed, currentMonitorWorkspaces, hideUnoccupied) [scroll_speed.bind('value'), options.bar.workspaces.hideUnoccupied.bind('value')],
(scroll_speed, hideUnoccupied) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(
scroll_speed,
currentMonitorWorkspaces,
hideUnoccupied,
);
self.on_scroll_up = throttledScrollUp; self.on_scroll_up = throttledScrollUp;
self.on_scroll_down = throttledScrollDown; self.on_scroll_down = throttledScrollDown;
}); },
} );
} },
},
}; };
}; };

View File

@@ -0,0 +1,43 @@
const hyprland = await Service.import('hyprland');
export const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
i: number,
): string => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return 'default';
};
export const renderLabel = (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
i: number,
index: number,
monitor: number,
): string => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
}
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (monitor !== -1) {
return available;
}
}
return workspaceMask ? `${index + 1}` : `${i}`;
};

View File

@@ -0,0 +1,99 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers';
import { range } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
const { workspaces, monitorSpecific, workspaceMask, spacing } = options.bar.workspaces;
export const defaultWses = (monitor: number): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[workspaces.bind('value'), monitorSpecific.bind()],
(workspaces: number, monitorSpecific: boolean) => {
return range(workspaces || 8)
.filter((i) => {
if (!monitorSpecific) {
return true;
}
const workspaceRules = getWorkspaceRules();
return getWorkspacesForMonitor(i, workspaceRules, monitor);
})
.sort((a, b) => {
return a - b;
})
.map((i, index) => {
return Widget.Button({
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: 'center',
css: spacing.bind('value').as((sp) => `margin: 0rem ${0.375 * sp}rem;`),
class_name: Utils.merge(
[
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
hyprland.active.workspace.bind('id'),
],
(showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string) => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd =
hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return 'default';
},
),
label: Utils.merge(
[
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
workspaceMask.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
) => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
}
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (monitor !== -1) {
return available;
}
}
return workspaceMask ? `${index + 1}` : `${i}`;
},
),
setup: (self) => {
self.hook(hyprland, () => {
self.toggleClassName('active', hyprland.active.workspace.id === i);
self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
});
},
}),
});
});
},
),
});
};

View File

@@ -0,0 +1,114 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers';
import { Workspace } from 'types/service/hyprland';
import { renderClassnames, renderLabel } from '../utils';
import { range } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
const { workspaces, monitorSpecific, workspaceMask, spacing } = options.bar.workspaces;
export const occupiedWses = (monitor: number): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[
monitorSpecific.bind('value'),
hyprland.bind('workspaces'),
workspaceMask.bind('value'),
workspaces.bind('value'),
options.bar.workspaces.show_icons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
options.bar.workspaces.icons.occupied.bind('value'),
options.bar.workspaces.show_numbered.bind('value'),
options.bar.workspaces.numbered_active_indicator.bind('value'),
spacing.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
monitorSpecific: boolean,
wkSpaces: Workspace[],
workspaceMask: boolean,
totalWkspcs: number,
showIcons: boolean,
available: string,
active: string,
occupied: string,
showNumbered: boolean,
numberedActiveIndicator: string,
spacing: number,
activeId: number,
) => {
let allWkspcs = range(totalWkspcs || 8);
const activeWorkspaces = wkSpaces.map((w) => w.id);
const workspaceRules = getWorkspaceRules();
// Sometimes hyprland doesn't have all the monitors in the list
// so we complement it with monitors from the workspace list
const workspaceMonitorList = hyprland?.workspaces?.map((m) => ({ id: m.monitorID, name: m.monitor }));
const curMonitor =
hyprland.monitors.find((m) => m.id === monitor) ||
workspaceMonitorList.find((m) => m.id === monitor);
// go through each key in workspaceRules and flatten the array
const workspacesWithRules = Object.keys(workspaceRules).reduce((acc: number[], k: string) => {
return [...acc, ...workspaceRules[k]];
}, [] as number[]);
const activesForMonitor = activeWorkspaces.filter((w) => {
if (
curMonitor &&
Object.hasOwnProperty.call(workspaceRules, curMonitor.name) &&
workspacesWithRules.includes(w)
) {
return workspaceRules[curMonitor.name].includes(w);
}
return true;
});
if (monitorSpecific) {
const wrkspcsInRange = range(totalWkspcs).filter((w) => {
return getWorkspacesForMonitor(w, workspaceRules, monitor);
});
allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])];
} else {
allWkspcs = [...new Set([...allWkspcs, ...activeWorkspaces])];
}
return allWkspcs
.sort((a, b) => {
return a - b;
})
.map((i, index) => {
return Widget.Button({
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: 'center',
css: `margin: 0rem ${0.375 * spacing}rem;`,
class_name: renderClassnames(showIcons, showNumbered, numberedActiveIndicator, i),
label: renderLabel(
showIcons,
available,
active,
occupied,
workspaceMask,
i,
index,
monitor,
),
setup: (self) => {
self.toggleClassName('active', activeId === i);
self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
},
}),
});
});
},
),
});
};

View File

@@ -1,199 +1,199 @@
export const substitutes = { export const substitutes = {
"transmission-gtk": "transmission", 'transmission-gtk': 'transmission',
"blueberry.py": "blueberry", 'blueberry.py': 'blueberry',
Caprine: "facebook-messenger", Caprine: 'facebook-messenger',
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic", 'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic", 'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
"audio-headset-bluetooth": "audio-headphones-symbolic", 'audio-headset-bluetooth': 'audio-headphones-symbolic',
"audio-card-analog-usb": "audio-speakers-symbolic", 'audio-card-analog-usb': 'audio-speakers-symbolic',
"audio-card-analog-pci": "audio-card-symbolic", 'audio-card-analog-pci': 'audio-card-symbolic',
"preferences-system": "emblem-system-symbolic", 'preferences-system': 'emblem-system-symbolic',
"com.github.Aylur.ags-symbolic": "controls-symbolic", 'com.github.Aylur.ags-symbolic': 'controls-symbolic',
"com.github.Aylur.ags": "controls-symbolic", 'com.github.Aylur.ags': 'controls-symbolic',
}; };
export default { export default {
missing: "image-missing-symbolic", missing: 'image-missing-symbolic',
nix: { nix: {
nix: "nix-snowflake-symbolic", nix: 'nix-snowflake-symbolic',
}, },
app: { app: {
terminal: "terminal-symbolic", terminal: 'terminal-symbolic',
}, },
fallback: { fallback: {
executable: "application-x-executable", executable: 'application-x-executable',
notification: "dialog-information-symbolic", notification: 'dialog-information-symbolic',
video: "video-x-generic-symbolic", video: 'video-x-generic-symbolic',
audio: "audio-x-generic-symbolic", audio: 'audio-x-generic-symbolic',
}, },
ui: { ui: {
close: "window-close-symbolic", close: 'window-close-symbolic',
colorpicker: "color-select-symbolic", colorpicker: 'color-select-symbolic',
info: "info-symbolic", info: 'info-symbolic',
link: "external-link-symbolic", link: 'external-link-symbolic',
lock: "system-lock-screen-symbolic", lock: 'system-lock-screen-symbolic',
menu: "open-menu-symbolic", menu: 'open-menu-symbolic',
refresh: "view-refresh-symbolic", refresh: 'view-refresh-symbolic',
search: "system-search-symbolic", search: 'system-search-symbolic',
settings: "emblem-system-symbolic", settings: 'emblem-system-symbolic',
themes: "preferences-desktop-theme-symbolic", themes: 'preferences-desktop-theme-symbolic',
tick: "object-select-symbolic", tick: 'object-select-symbolic',
time: "hourglass-symbolic", time: 'hourglass-symbolic',
toolbars: "toolbars-symbolic", toolbars: 'toolbars-symbolic',
warning: "dialog-warning-symbolic", warning: 'dialog-warning-symbolic',
avatar: "avatar-default-symbolic", avatar: 'avatar-default-symbolic',
arrow: { arrow: {
right: "pan-end-symbolic", right: 'pan-end-symbolic',
left: "pan-start-symbolic", left: 'pan-start-symbolic',
down: "pan-down-symbolic", down: 'pan-down-symbolic',
up: "pan-up-symbolic", up: 'pan-up-symbolic',
}, },
}, },
audio: { audio: {
mic: { mic: {
muted: "microphone-disabled-symbolic", muted: 'microphone-disabled-symbolic',
low: "microphone-sensitivity-low-symbolic", low: 'microphone-sensitivity-low-symbolic',
medium: "microphone-sensitivity-medium-symbolic", medium: 'microphone-sensitivity-medium-symbolic',
high: "microphone-sensitivity-high-symbolic", high: 'microphone-sensitivity-high-symbolic',
}, },
volume: { volume: {
muted: "audio-volume-muted-symbolic", muted: 'audio-volume-muted-symbolic',
low: "audio-volume-low-symbolic", low: 'audio-volume-low-symbolic',
medium: "audio-volume-medium-symbolic", medium: 'audio-volume-medium-symbolic',
high: "audio-volume-high-symbolic", high: 'audio-volume-high-symbolic',
overamplified: "audio-volume-overamplified-symbolic", overamplified: 'audio-volume-overamplified-symbolic',
}, },
type: { type: {
headset: "audio-headphones-symbolic", headset: 'audio-headphones-symbolic',
speaker: "audio-speakers-symbolic", speaker: 'audio-speakers-symbolic',
card: "audio-card-symbolic", card: 'audio-card-symbolic',
}, },
mixer: "mixer-symbolic", mixer: 'mixer-symbolic',
}, },
powerprofile: { powerprofile: {
balanced: "power-profile-balanced-symbolic", balanced: 'power-profile-balanced-symbolic',
"power-saver": "power-profile-power-saver-symbolic", 'power-saver': 'power-profile-power-saver-symbolic',
performance: "power-profile-performance-symbolic", performance: 'power-profile-performance-symbolic',
}, },
asusctl: { asusctl: {
profile: { profile: {
Balanced: "power-profile-balanced-symbolic", Balanced: 'power-profile-balanced-symbolic',
Quiet: "power-profile-power-saver-symbolic", Quiet: 'power-profile-power-saver-symbolic',
Performance: "power-profile-performance-symbolic", Performance: 'power-profile-performance-symbolic',
}, },
mode: { mode: {
Integrated: "processor-symbolic", Integrated: 'processor-symbolic',
Hybrid: "controller-symbolic", Hybrid: 'controller-symbolic',
}, },
}, },
battery: { battery: {
charging: "battery-flash-symbolic", charging: 'battery-flash-symbolic',
warning: "battery-empty-symbolic", warning: 'battery-empty-symbolic',
}, },
bluetooth: { bluetooth: {
enabled: "bluetooth-active-symbolic", enabled: 'bluetooth-active-symbolic',
disabled: "bluetooth-disabled-symbolic", disabled: 'bluetooth-disabled-symbolic',
}, },
brightness: { brightness: {
indicator: "display-brightness-symbolic", indicator: 'display-brightness-symbolic',
keyboard: "keyboard-brightness-symbolic", keyboard: 'keyboard-brightness-symbolic',
screen: "display-brightness-symbolic", screen: 'display-brightness-symbolic',
}, },
powermenu: { powermenu: {
sleep: "weather-clear-night-symbolic", sleep: 'weather-clear-night-symbolic',
reboot: "system-reboot-symbolic", reboot: 'system-reboot-symbolic',
logout: "system-log-out-symbolic", logout: 'system-log-out-symbolic',
shutdown: "system-shutdown-symbolic", shutdown: 'system-shutdown-symbolic',
}, },
recorder: { recorder: {
recording: "media-record-symbolic", recording: 'media-record-symbolic',
}, },
notifications: { notifications: {
noisy: "org.gnome.Settings-notifications-symbolic", noisy: 'org.gnome.Settings-notifications-symbolic',
silent: "notifications-disabled-symbolic", silent: 'notifications-disabled-symbolic',
message: "chat-bubbles-symbolic", message: 'chat-bubbles-symbolic',
}, },
trash: { trash: {
full: "user-trash-full-symbolic", full: 'user-trash-full-symbolic',
empty: "user-trash-symbolic", empty: 'user-trash-symbolic',
}, },
mpris: { mpris: {
shuffle: { shuffle: {
enabled: "media-playlist-shuffle-symbolic", enabled: 'media-playlist-shuffle-symbolic',
disabled: "media-playlist-consecutive-symbolic", disabled: 'media-playlist-consecutive-symbolic',
}, },
loop: { loop: {
none: "media-playlist-repeat-symbolic", none: 'media-playlist-repeat-symbolic',
track: "media-playlist-repeat-song-symbolic", track: 'media-playlist-repeat-song-symbolic',
playlist: "media-playlist-repeat-symbolic", playlist: 'media-playlist-repeat-symbolic',
}, },
playing: "media-playback-pause-symbolic", playing: 'media-playback-pause-symbolic',
paused: "media-playback-start-symbolic", paused: 'media-playback-start-symbolic',
stopped: "media-playback-start-symbolic", stopped: 'media-playback-start-symbolic',
prev: "media-skip-backward-symbolic", prev: 'media-skip-backward-symbolic',
next: "media-skip-forward-symbolic", next: 'media-skip-forward-symbolic',
}, },
system: { system: {
cpu: "org.gnome.SystemMonitor-symbolic", cpu: 'org.gnome.SystemMonitor-symbolic',
ram: "drive-harddisk-solidstate-symbolic", ram: 'drive-harddisk-solidstate-symbolic',
temp: "temperature-symbolic", temp: 'temperature-symbolic',
}, },
color: { color: {
dark: "dark-mode-symbolic", dark: 'dark-mode-symbolic',
light: "light-mode-symbolic", light: 'light-mode-symbolic',
}, },
weather: { weather: {
warning: "dialog-warning-symbolic", warning: 'dialog-warning-symbolic',
sunny: "weather-clear-symbolic", sunny: 'weather-clear-symbolic',
clear: "weather-clear-night-symbolic", clear: 'weather-clear-night-symbolic',
partly_cloudy: "weather-few-clouds-symbolic", partly_cloudy: 'weather-few-clouds-symbolic',
partly_cloudy_night: "weather-few-clouds-night-symbolic", partly_cloudy_night: 'weather-few-clouds-night-symbolic',
cloudy: "weather-overcast-symbolic", cloudy: 'weather-overcast-symbolic',
overcast: "weather-overcast-symbolic", overcast: 'weather-overcast-symbolic',
mist: "weather-overcast-symbolic", mist: 'weather-overcast-symbolic',
patchy_rain_nearby: "weather-showers-scattered-symbolic", patchy_rain_nearby: 'weather-showers-scattered-symbolic',
patchy_rain_possible: "weather-showers-scattered-symbolic", patchy_rain_possible: 'weather-showers-scattered-symbolic',
patchy_snow_possible: "weather-snow-symbolic", patchy_snow_possible: 'weather-snow-symbolic',
patchy_sleet_possible: "weather-snow-symbolic", patchy_sleet_possible: 'weather-snow-symbolic',
patchy_freezing_drizzle_possible: "weather-showers-scattered-symbolic", patchy_freezing_drizzle_possible: 'weather-showers-scattered-symbolic',
thundery_outbreaks_possible: "weather-overcast-symbolic", thundery_outbreaks_possible: 'weather-overcast-symbolic',
blowing_snow: "weather-snow-symbolic", blowing_snow: 'weather-snow-symbolic',
blizzard: "weather-snow-symbolic", blizzard: 'weather-snow-symbolic',
fog: "weather-fog-symbolic", fog: 'weather-fog-symbolic',
freezing_fog: "weather-fog-symbolic", freezing_fog: 'weather-fog-symbolic',
patchy_light_drizzle: "weather-showers-scattered-symbolic", patchy_light_drizzle: 'weather-showers-scattered-symbolic',
light_drizzle: "weather-showers-symbolic", light_drizzle: 'weather-showers-symbolic',
freezing_drizzle: "weather-showers-symbolic", freezing_drizzle: 'weather-showers-symbolic',
heavy_freezing_drizzle: "weather-showers-symbolic", heavy_freezing_drizzle: 'weather-showers-symbolic',
patchy_light_rain: "weather-showers-scattered-symbolic", patchy_light_rain: 'weather-showers-scattered-symbolic',
light_rain: "weather-showers-symbolic", light_rain: 'weather-showers-symbolic',
moderate_rain_at_times: "weather-showers-symbolic", moderate_rain_at_times: 'weather-showers-symbolic',
moderate_rain: "weather-showers-symbolic", moderate_rain: 'weather-showers-symbolic',
heavy_rain_at_times: "weather-showers-symbolic", heavy_rain_at_times: 'weather-showers-symbolic',
heavy_rain: "weather-showers-symbolic", heavy_rain: 'weather-showers-symbolic',
light_freezing_rain: "weather-showers-symbolic", light_freezing_rain: 'weather-showers-symbolic',
moderate_or_heavy_freezing_rain: "weather-showers-symbolic", moderate_or_heavy_freezing_rain: 'weather-showers-symbolic',
light_sleet: "weather-snow-symbolic", light_sleet: 'weather-snow-symbolic',
moderate_or_heavy_sleet: "weather-snow-symbolic", moderate_or_heavy_sleet: 'weather-snow-symbolic',
patchy_light_snow: "weather-snow-symbolic", patchy_light_snow: 'weather-snow-symbolic',
light_snow: "weather-snow-symbolic", light_snow: 'weather-snow-symbolic',
patchy_moderate_snow: "weather-snow-symbolic", patchy_moderate_snow: 'weather-snow-symbolic',
moderate_snow: "weather-snow-symbolic", moderate_snow: 'weather-snow-symbolic',
patchy_heavy_snow: "weather-snow-symbolic", patchy_heavy_snow: 'weather-snow-symbolic',
heavy_snow: "weather-snow-symbolic", heavy_snow: 'weather-snow-symbolic',
ice_pellets: "weather-showers-symbolic", ice_pellets: 'weather-showers-symbolic',
light_rain_shower: "weather-showers-symbolic", light_rain_shower: 'weather-showers-symbolic',
moderate_or_heavy_rain_shower: "weather-showers-symbolic", moderate_or_heavy_rain_shower: 'weather-showers-symbolic',
torrential_rain_shower: "weather-showers-symbolic", torrential_rain_shower: 'weather-showers-symbolic',
light_sleet_showers: "weather-showers-symbolic", light_sleet_showers: 'weather-showers-symbolic',
moderate_or_heavy_sleet_showers: "weather-showers-symbolic", moderate_or_heavy_sleet_showers: 'weather-showers-symbolic',
light_snow_showers: "weather-snow-symbolic", light_snow_showers: 'weather-snow-symbolic',
moderate_or_heavy_snow_showers: "weather-snow-symbolic", moderate_or_heavy_snow_showers: 'weather-snow-symbolic',
light_showers_of_ice_pellets: "weather-showers-symbolic", light_showers_of_ice_pellets: 'weather-showers-symbolic',
moderate_or_heavy_showers_of_ice_pellets: "weather-showers-symbolic", moderate_or_heavy_showers_of_ice_pellets: 'weather-showers-symbolic',
patchy_light_rain_with_thunder: "weather-showers-scattered-symbolic", patchy_light_rain_with_thunder: 'weather-showers-scattered-symbolic',
moderate_or_heavy_rain_with_thunder: "weather-showers-symbolic", moderate_or_heavy_rain_with_thunder: 'weather-showers-symbolic',
patchy_light_snow_with_thunder: "weather-snow-symbolic", patchy_light_snow_with_thunder: 'weather-snow-symbolic',
moderate_or_heavy_snow_with_thunder: "weather-snow-symbolic", moderate_or_heavy_snow_with_thunder: 'weather-snow-symbolic',
}, },
} as const; } as const;

View File

@@ -1,54 +1,54 @@
export const weatherIcons = { export const weatherIcons = {
warning: "󰼯", warning: '󰼯',
sunny: "󰖙", sunny: '󰖙',
clear: "󰖔", clear: '󰖔',
partly_cloudy: "󰖕", partly_cloudy: '󰖕',
partly_cloudy_night: "󰼱", partly_cloudy_night: '󰼱',
cloudy: "󰖐", cloudy: '󰖐',
overcast: "󰖕", overcast: '󰖕',
mist: "󰖑", mist: '󰖑',
patchy_rain_nearby: "󰼳", patchy_rain_nearby: '󰼳',
patchy_rain_possible: "󰼳", patchy_rain_possible: '󰼳',
patchy_snow_possible: "󰼴", patchy_snow_possible: '󰼴',
patchy_sleet_possible: "󰙿", patchy_sleet_possible: '󰙿',
patchy_freezing_drizzle_possible: "󰙿", patchy_freezing_drizzle_possible: '󰙿',
thundery_outbreaks_possible: "󰙾", thundery_outbreaks_possible: '󰙾',
blowing_snow: "󰼶", blowing_snow: '󰼶',
blizzard: "󰼶", blizzard: '󰼶',
fog: "󰖑", fog: '󰖑',
freezing_fog: "󰖑", freezing_fog: '󰖑',
patchy_light_drizzle: "󰼳", patchy_light_drizzle: '󰼳',
light_drizzle: "󰼳", light_drizzle: '󰼳',
freezing_drizzle: "󰙿", freezing_drizzle: '󰙿',
heavy_freezing_drizzle: "󰙿", heavy_freezing_drizzle: '󰙿',
patchy_light_rain: "󰼳", patchy_light_rain: '󰼳',
light_rain: "󰼳", light_rain: '󰼳',
moderate_rain_at_times: "󰖗", moderate_rain_at_times: '󰖗',
moderate_rain: "󰼳", moderate_rain: '󰼳',
heavy_rain_at_times: "󰖖", heavy_rain_at_times: '󰖖',
heavy_rain: "󰖖", heavy_rain: '󰖖',
light_freezing_rain: "󰙿", light_freezing_rain: '󰙿',
moderate_or_heavy_freezing_rain: "󰙿", moderate_or_heavy_freezing_rain: '󰙿',
light_sleet: "󰙿", light_sleet: '󰙿',
moderate_or_heavy_sleet: "󰙿", moderate_or_heavy_sleet: '󰙿',
patchy_light_snow: "󰼴", patchy_light_snow: '󰼴',
light_snow: "󰼴", light_snow: '󰼴',
patchy_moderate_snow: "󰼴", patchy_moderate_snow: '󰼴',
moderate_snow: "󰼶", moderate_snow: '󰼶',
patchy_heavy_snow: "󰼶", patchy_heavy_snow: '󰼶',
heavy_snow: "󰼶", heavy_snow: '󰼶',
ice_pellets: "󰖒", ice_pellets: '󰖒',
light_rain_shower: "󰖖", light_rain_shower: '󰖖',
moderate_or_heavy_rain_shower: "󰖖", moderate_or_heavy_rain_shower: '󰖖',
torrential_rain_shower: "󰖖", torrential_rain_shower: '󰖖',
light_sleet_showers: "󰼵", light_sleet_showers: '󰼵',
moderate_or_heavy_sleet_showers: "󰼵", moderate_or_heavy_sleet_showers: '󰼵',
light_snow_showers: "󰼵", light_snow_showers: '󰼵',
moderate_or_heavy_snow_showers: "󰼵", moderate_or_heavy_snow_showers: '󰼵',
light_showers_of_ice_pellets: "󰖒", light_showers_of_ice_pellets: '󰖒',
moderate_or_heavy_showers_of_ice_pellets: "󰖒", moderate_or_heavy_showers_of_ice_pellets: '󰖒',
patchy_light_rain_with_thunder: "󰙾", patchy_light_rain_with_thunder: '󰙾',
moderate_or_heavy_rain_with_thunder: "󰙾", moderate_or_heavy_rain_with_thunder: '󰙾',
patchy_light_snow_with_thunder: "󰼶", patchy_light_snow_with_thunder: '󰼶',
moderate_or_heavy_snow_with_thunder: "󰼶", moderate_or_heavy_snow_with_thunder: '󰼶',
} as const; } as const;

View File

@@ -1,25 +1,34 @@
const hyprland = await Service.import("hyprland"); const hyprland = await Service.import('hyprland');
import { DropdownMenuProps } from "lib/types/dropdownmenu"; import { DropdownMenuProps } from 'lib/types/dropdownmenu';
import { Exclusivity } from "lib/types/widget"; import { Attribute, Child, Exclusivity, GtkWidget } from 'lib/types/widget';
import { bash } from "lib/utils"; import { bash } from 'lib/utils';
import { Monitor } from "types/service/hyprland"; import { Widget as TWidget } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { Monitor } from 'types/service/hyprland';
import Box from 'types/widgets/box';
import EventBox from 'types/widgets/eventbox';
import Revealer from 'types/widgets/revealer';
import Window from 'types/widgets/window';
export const Padding = (name: string) => type NestedRevealer = Revealer<Box<TWidget, unknown>, unknown>;
type NestedBox = Box<NestedRevealer, unknown>;
type NestedEventBox = EventBox<NestedBox, unknown>;
export const Padding = (name: string): EventBox<Box<GtkWidget, Attribute>, Attribute> =>
Widget.EventBox({ Widget.EventBox({
hexpand: true, hexpand: true,
vexpand: true, vexpand: true,
can_focus: true, can_focus: true,
child: Widget.Box(), child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), setup: (w) => w.on('button-press-event', () => App.toggleWindow(name)),
}); });
const moveBoxToCursor = (self: any, fixed: boolean) => { const moveBoxToCursor = <T extends NestedEventBox>(self: T, fixed: boolean): void => {
if (fixed) { if (fixed) {
return; return;
} }
globalMousePos.connect("changed", async ({ value }) => { globalMousePos.connect('changed', async ({ value }) => {
const curHyprlandMonitor = hyprland.monitors.find(m => m.id === hyprland.active.monitor.id); const curHyprlandMonitor = hyprland.monitors.find((m) => m.id === hyprland.active.monitor.id);
const dropdownWidth = self.child.get_allocation().width; const dropdownWidth = self.child.get_allocation().width;
let hyprScaling = 1; let hyprScaling = 1;
@@ -27,8 +36,8 @@ const moveBoxToCursor = (self: any, fixed: boolean) => {
const monitorInfo = await bash('hyprctl monitors -j'); const monitorInfo = await bash('hyprctl monitors -j');
const parsedMonitorInfo = JSON.parse(monitorInfo); const parsedMonitorInfo = JSON.parse(monitorInfo);
const foundMonitor = parsedMonitorInfo.find((monitor: Monitor) => const foundMonitor = parsedMonitorInfo.find(
monitor.id === hyprland.active.monitor.id (monitor: Monitor) => monitor.id === hyprland.active.monitor.id,
); );
hyprScaling = foundMonitor?.scale || 1; hyprScaling = foundMonitor?.scale || 1;
} catch (error) { } catch (error) {
@@ -58,9 +67,7 @@ const moveBoxToCursor = (self: any, fixed: boolean) => {
} }
// If monitor is vertical (transform = 1 || 3) swap height and width // If monitor is vertical (transform = 1 || 3) swap height and width
const isVertical = curHyprlandMonitor?.transform !== undefined const isVertical = curHyprlandMonitor?.transform !== undefined ? curHyprlandMonitor.transform % 2 !== 0 : false;
? curHyprlandMonitor.transform % 2 !== 0
: false;
if (isVertical) { if (isVertical) {
[monWidth, monHeight] = [monHeight, monWidth]; [monWidth, monHeight] = [monHeight, monWidth];
@@ -100,58 +107,55 @@ setTimeout(() => {
initRender.value = false; initRender.value = false;
}, 2000); }, 2000);
export default ( export default ({
{
name, name,
child, child,
layout = "center",
transition, transition,
exclusivity = "ignore" as Exclusivity, exclusivity = 'ignore' as Exclusivity,
fixed = false, fixed = false,
...props ...props
}: DropdownMenuProps }: DropdownMenuProps): Window<Child, Attribute> =>
) =>
Widget.Window({ Widget.Window({
name, name,
class_names: [name, "dropdown-menu"], class_names: [name, 'dropdown-menu'],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), setup: (w) => w.keybind('Escape', () => App.closeWindow(name)),
visible: initRender.bind("value"), visible: initRender.bind('value'),
keymode: "on-demand", keymode: 'on-demand',
exclusivity, exclusivity,
layer: "top", layer: 'top',
anchor: ["top", "left"], anchor: ['top', 'left'],
child: Widget.EventBox({ child: Widget.EventBox({
class_name: "parent-event", class_name: 'parent-event',
on_primary_click: () => App.closeWindow(name), on_primary_click: () => App.closeWindow(name),
on_secondary_click: () => App.closeWindow(name), on_secondary_click: () => App.closeWindow(name),
child: Widget.Box({ child: Widget.Box({
class_name: "top-eb", class_name: 'top-eb',
vertical: true, vertical: true,
children: [ children: [
Widget.EventBox({ Widget.EventBox({
class_name: "mid-eb event-top-padding-static", class_name: 'mid-eb event-top-padding-static',
hexpand: true, hexpand: true,
vexpand: false, vexpand: false,
can_focus: false, can_focus: false,
child: Widget.Box(), child: Widget.Box(),
setup: (w) => { setup: (w) => {
w.on("button-press-event", () => App.toggleWindow(name)); w.on('button-press-event', () => App.toggleWindow(name));
w.set_margin_top(1); w.set_margin_top(1);
}, },
}), }),
Widget.EventBox({ Widget.EventBox({
class_name: "mid-eb event-top-padding", class_name: 'mid-eb event-top-padding',
hexpand: true, hexpand: true,
vexpand: false, vexpand: false,
can_focus: false, can_focus: false,
child: Widget.Box(), child: Widget.Box(),
setup: (w) => { setup: (w) => {
w.on("button-press-event", () => App.toggleWindow(name)); w.on('button-press-event', () => App.toggleWindow(name));
w.set_margin_top(1); w.set_margin_top(1);
}, },
}), }),
Widget.EventBox({ Widget.EventBox({
class_name: "in-eb menu-event-box", class_name: 'in-eb menu-event-box',
on_primary_click: () => { on_primary_click: () => {
return true; return true;
}, },
@@ -162,18 +166,18 @@ export default (
moveBoxToCursor(self, fixed); moveBoxToCursor(self, fixed);
}, },
child: Widget.Box({ child: Widget.Box({
class_name: "dropdown-menu-container", class_name: 'dropdown-menu-container',
css: "padding: 1px; margin: -1px;", css: 'padding: 1px; margin: -1px;',
child: Widget.Revealer({ child: Widget.Revealer({
revealChild: false, revealChild: false,
setup: (self) => setup: (self) =>
self.hook(App, (_, wname, visible) => { self.hook(App, (_, wname, visible) => {
if (wname === name) self.reveal_child = visible; if (wname === name) self.reveal_child = visible;
}), }),
transition: "crossfade", transition,
transitionDuration: 350, transitionDuration: 350,
child: Widget.Box({ child: Widget.Box({
class_name: "dropdown-menu-container", class_name: 'dropdown-menu-container',
can_focus: true, can_focus: true,
children: [child], children: [child],
}), }),

View File

@@ -1,25 +1,32 @@
import { WINDOW_LAYOUTS } from "globals/window"; import { WINDOW_LAYOUTS } from 'globals/window';
import { LayoutFunction, Layouts, PopupWindowProps } from "lib/types/popupwindow"; import { LayoutFunction, Layouts, PopupWindowProps } from 'lib/types/popupwindow';
import { Exclusivity, Transition } from "lib/types/widget"; import { Attribute, Child, Exclusivity, GtkWidget, Transition } from 'lib/types/widget';
import Box from 'types/widgets/box';
import EventBox from 'types/widgets/eventbox';
import Window from 'types/widgets/window';
type Opts = { type Opts = {
className: string className: string;
vexpand: boolean vexpand: boolean;
} };
export const Padding = (name: string, opts: Opts) => export const Padding = (name: string, opts: Opts): EventBox<Box<GtkWidget, Attribute>, unknown> =>
Widget.EventBox({ Widget.EventBox({
class_name: opts?.className || "", class_name: opts?.className || '',
hexpand: true, hexpand: true,
vexpand: typeof opts?.vexpand === "boolean" ? opts.vexpand : true, vexpand: typeof opts?.vexpand === 'boolean' ? opts.vexpand : true,
can_focus: false, can_focus: false,
child: Widget.Box(), child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)), setup: (w) => w.on('button-press-event', () => App.toggleWindow(name)),
}); });
const PopupRevealer = (name: string, child: any, transition = "slide_down" as Transition) => const PopupRevealer = (
name: string,
child: GtkWidget,
transition = 'slide_down' as Transition,
): Box<Child, Attribute> =>
Widget.Box( Widget.Box(
{ css: "padding: 1px;" }, { css: 'padding: 1px;' },
Widget.Revealer({ Widget.Revealer({
transition, transition,
child: Widget.Box({ child: Widget.Box({
@@ -34,7 +41,7 @@ const PopupRevealer = (name: string, child: any, transition = "slide_down" as Tr
}), }),
); );
const Layout: LayoutFunction = (name: string, child: any, transition: Transition) => ({ const Layout: LayoutFunction = (name: string, child: GtkWidget, transition: Transition) => ({
center: () => center: () =>
Widget.CenterBox( Widget.CenterBox(
{}, {},
@@ -51,14 +58,10 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
Widget.CenterBox( Widget.CenterBox(
{}, {},
Padding(name, {} as Opts), Padding(name, {} as Opts),
Widget.Box( Widget.Box({ vertical: true }, PopupRevealer(name, child, transition), Padding(name, {} as Opts)),
{ vertical: true },
PopupRevealer(name, child, transition),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
Padding(name, {} as Opts), 'top-right': () =>
),
"top-right": () =>
Widget.Box( Widget.Box(
{}, {},
Padding(name, {} as Opts), Padding(name, {} as Opts),
@@ -69,13 +72,13 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
}, },
Padding(name, { Padding(name, {
vexpand: false, vexpand: false,
className: "event-top-padding", className: 'event-top-padding',
}), }),
PopupRevealer(name, child, transition), PopupRevealer(name, child, transition),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
), ),
"top-center": () => 'top-center': () =>
Widget.Box( Widget.Box(
{}, {},
Padding(name, {} as Opts), Padding(name, {} as Opts),
@@ -86,14 +89,14 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
}, },
Padding(name, { Padding(name, {
vexpand: false, vexpand: false,
className: "event-top-padding", className: 'event-top-padding',
}), }),
PopupRevealer(name, child, transition), PopupRevealer(name, child, transition),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
"top-left": () => 'top-left': () =>
Widget.Box( Widget.Box(
{}, {},
Widget.Box( Widget.Box(
@@ -103,14 +106,14 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
}, },
Padding(name, { Padding(name, {
vexpand: false, vexpand: false,
className: "event-top-padding", className: 'event-top-padding',
}), }),
PopupRevealer(name, child, transition), PopupRevealer(name, child, transition),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
"bottom-left": () => 'bottom-left': () =>
Widget.Box( Widget.Box(
{}, {},
Widget.Box( Widget.Box(
@@ -123,7 +126,7 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
), ),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
"bottom-center": () => 'bottom-center': () =>
Widget.Box( Widget.Box(
{}, {},
Padding(name, {} as Opts), Padding(name, {} as Opts),
@@ -137,7 +140,7 @@ const Layout: LayoutFunction = (name: string, child: any, transition: Transition
), ),
Padding(name, {} as Opts), Padding(name, {} as Opts),
), ),
"bottom-right": () => 'bottom-right': () =>
Widget.Box( Widget.Box(
{}, {},
Padding(name, {} as Opts), Padding(name, {} as Opts),
@@ -159,25 +162,25 @@ const isValidLayout = (layout: string): layout is Layouts => {
export default ({ export default ({
name, name,
child, child,
layout = "center", layout = 'center',
transition, transition,
exclusivity = "ignore" as Exclusivity, exclusivity = 'ignore' as Exclusivity,
...props ...props
}: PopupWindowProps) => { }: PopupWindowProps): Window<Child, Attribute> => {
const layoutFn = isValidLayout(layout) ? layout : "center"; const layoutFn = isValidLayout(layout) ? layout : 'center';
const layoutWidget = Layout(name, child, transition)[layoutFn](); const layoutWidget = Layout(name, child, transition)[layoutFn]();
return Widget.Window({ return Widget.Window({
name, name,
class_names: [name, "popup-window"], class_names: [name, 'popup-window'],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)), setup: (w) => w.keybind('Escape', () => App.closeWindow(name)),
visible: false, visible: false,
keymode: "on-demand", keymode: 'on-demand',
exclusivity, exclusivity,
layer: "top", layer: 'top',
anchor: ["top", "bottom", "right", "left"], anchor: ['top', 'bottom', 'right', 'left'],
child: layoutWidget, child: layoutWidget,
...props, ...props,
}); });
} };

View File

@@ -1,31 +1,36 @@
const audio = await Service.import("audio"); const audio = await Service.import('audio');
import { BarBoxChild } from 'lib/types/bar.js';
import { getIcon } from '../utils.js'; import { getIcon } from '../utils.js';
const renderActiveInput = () => { const renderActiveInput = (): BarBoxChild => {
return [ return [
Widget.Box({ Widget.Box({
class_name: "menu-slider-container input", class_name: 'menu-slider-container input',
children: [ children: [
Widget.Button({ Widget.Button({
vexpand: false, vexpand: false,
vpack: "end", vpack: 'end',
setup: (self) => { setup: (self) => {
self.hook(audio, () => { self.hook(audio, () => {
const mic = audio.microphone; const mic = audio.microphone;
const className = `menu-active-button input ${mic.is_muted ? "muted" : ""}`; const className = `menu-active-button input ${mic.is_muted ? 'muted' : ''}`;
return (self.class_name = className); return (self.class_name = className);
}); });
}, },
on_primary_click: () => on_primary_click: () => (audio.microphone.is_muted = !audio.microphone.is_muted),
(audio.microphone.is_muted = !audio.microphone.is_muted),
child: Widget.Icon({ child: Widget.Icon({
class_name: "menu-active-icon input", class_name: 'menu-active-icon input',
setup: (self) => { setup: (self) => {
self.hook(audio, () => { self.hook(audio, () => {
self.icon = getIcon( const isMicMuted =
audio.microphone.volume, audio.microphone.is_muted !== null ? audio.microphone.is_muted : true;
audio.microphone.is_muted,
)["mic"]; if (audio.microphone.volume > 0) {
self.icon = getIcon(audio.microphone.volume, isMicMuted)['mic'];
return;
}
self.icon = getIcon(100, false)['mic'];
}); });
}, },
}), }),
@@ -34,15 +39,17 @@ const renderActiveInput = () => {
vertical: true, vertical: true,
children: [ children: [
Widget.Label({ Widget.Label({
class_name: "menu-active input", class_name: 'menu-active input',
hpack: "start", hpack: 'start',
truncate: "end", truncate: 'end',
wrap: true, wrap: true,
label: audio.bind("microphone").as((v) => v.description === null ? "No input device found..." : v.description), label: audio
.bind('microphone')
.as((v) => (v.description === null ? 'No input device found...' : v.description)),
}), }),
Widget.Slider({ Widget.Slider({
value: audio.microphone.bind("volume").as((v) => v), value: audio.microphone.bind('volume').as((v) => v),
class_name: "menu-active-slider menu-slider inputs", class_name: 'menu-active-slider menu-slider inputs',
draw_value: false, draw_value: false,
hexpand: true, hexpand: true,
min: 0, min: 0,
@@ -52,11 +59,9 @@ const renderActiveInput = () => {
], ],
}), }),
Widget.Label({ Widget.Label({
class_name: "menu-active-percentage input", class_name: 'menu-active-percentage input',
vpack: "end", vpack: 'end',
label: audio.microphone label: audio.microphone.bind('volume').as((v) => `${Math.round(v * 100)}%`),
.bind("volume")
.as((v) => `${Math.round(v * 100)}%`),
}), }),
], ],
}), }),

View File

@@ -1,31 +1,29 @@
const audio = await Service.import("audio"); const audio = await Service.import('audio');
import { getIcon } from "../utils.js"; import { BarBoxChild } from 'lib/types/bar.js';
import { getIcon } from '../utils.js';
const renderActivePlayback = () => { const renderActivePlayback = (): BarBoxChild => {
return [ return [
Widget.Box({ Widget.Box({
class_name: "menu-slider-container playback", class_name: 'menu-slider-container playback',
children: [ children: [
Widget.Button({ Widget.Button({
vexpand: false, vexpand: false,
vpack: "end", vpack: 'end',
setup: (self) => { setup: (self) => {
self.hook(audio, () => { self.hook(audio, () => {
const spkr = audio.speaker; const spkr = audio.speaker;
const className = `menu-active-button playback ${spkr.is_muted ? "muted" : ""}`; const className = `menu-active-button playback ${spkr.is_muted ? 'muted' : ''}`;
return (self.class_name = className); return (self.class_name = className);
}); });
}, },
on_primary_click: () => on_primary_click: () => (audio.speaker.is_muted = !audio.speaker.is_muted),
(audio.speaker.is_muted = !audio.speaker.is_muted),
child: Widget.Icon({ child: Widget.Icon({
class_name: "menu-active-icon playback", class_name: 'menu-active-icon playback',
setup: (self) => { setup: (self) => {
self.hook(audio, () => { self.hook(audio, () => {
self.icon = getIcon( const isSpeakerMuted = audio.speaker.is_muted !== null ? audio.speaker.is_muted : true;
audio.speaker.volume, self.icon = getIcon(audio.speaker.volume, isSpeakerMuted)['spkr'];
audio.speaker.is_muted,
)["spkr"];
}); });
}, },
}), }),
@@ -34,16 +32,16 @@ const renderActivePlayback = () => {
vertical: true, vertical: true,
children: [ children: [
Widget.Label({ Widget.Label({
class_name: "menu-active playback", class_name: 'menu-active playback',
hpack: "start", hpack: 'start',
truncate: "end", truncate: 'end',
expand: true, expand: true,
wrap: true, wrap: true,
label: audio.bind("speaker").as((v) => v.description || ""), label: audio.bind('speaker').as((v) => v.description || ''),
}), }),
Widget.Slider({ Widget.Slider({
value: audio["speaker"].bind("volume"), value: audio['speaker'].bind('volume'),
class_name: "menu-active-slider menu-slider playback", class_name: 'menu-active-slider menu-slider playback',
draw_value: false, draw_value: false,
hexpand: true, hexpand: true,
min: 0, min: 0,
@@ -53,11 +51,9 @@ const renderActivePlayback = () => {
], ],
}), }),
Widget.Label({ Widget.Label({
vpack: "end", vpack: 'end',
class_name: "menu-active-percentage playback", class_name: 'menu-active-percentage playback',
label: audio.speaker label: audio.speaker.bind('volume').as((v) => `${Math.round(v * 100)}%`),
.bind("volume")
.as((v) => `${Math.round(v * 100)}%`),
}), }),
], ],
}), }),

View File

@@ -1,32 +1,33 @@
import { renderActiveInput } from "./SelectedInput.js"; import { BarBoxChild } from 'lib/types/bar.js';
import { renderActivePlayback } from "./SelectedPlayback.js"; import { renderActiveInput } from './SelectedInput.js';
import { renderActivePlayback } from './SelectedPlayback.js';
const activeDevices = () => { const activeDevices = (): BarBoxChild => {
return Widget.Box({ return Widget.Box({
class_name: "menu-section-container volume", class_name: 'menu-section-container volume',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
class_name: "menu-label-container volume selected", class_name: 'menu-label-container volume selected',
hpack: "fill", hpack: 'fill',
child: Widget.Label({ child: Widget.Label({
class_name: "menu-label audio volume", class_name: 'menu-label audio volume',
hexpand: true, hexpand: true,
hpack: "start", hpack: 'start',
label: "Volume", label: 'Volume',
}), }),
}), }),
Widget.Box({ Widget.Box({
class_name: "menu-items-section selected", class_name: 'menu-items-section selected',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
class_name: "menu-active-container playback", class_name: 'menu-active-container playback',
vertical: true, vertical: true,
children: renderActivePlayback(), children: renderActivePlayback(),
}), }),
Widget.Box({ Widget.Box({
class_name: "menu-active-container input", class_name: 'menu-active-container input',
vertical: true, vertical: true,
children: renderActiveInput(), children: renderActiveInput(),
}), }),

View File

@@ -1,7 +1,8 @@
const audio = await Service.import("audio"); const audio = await Service.import('audio');
import { Stream } from "types/service/audio"; import { InputDevices } from 'lib/types/audio';
import { Stream } from 'types/service/audio';
const renderInputDevices = (inputDevices: Stream[]) => { const renderInputDevices = (inputDevices: Stream[]): InputDevices => {
if (inputDevices.length === 0) { if (inputDevices.length === 0) {
return [ return [
Widget.Button({ Widget.Button({
@@ -9,11 +10,11 @@ const renderInputDevices = (inputDevices: Stream[]) => {
child: Widget.Box({ child: Widget.Box({
children: [ children: [
Widget.Box({ Widget.Box({
hpack: "start", hpack: 'start',
children: [ children: [
Widget.Label({ Widget.Label({
class_name: "menu-button-name input", class_name: 'menu-button-name input',
label: "No input devices found...", label: 'No input devices found...',
}), }),
], ],
}), }),
@@ -29,28 +30,28 @@ const renderInputDevices = (inputDevices: Stream[]) => {
child: Widget.Box({ child: Widget.Box({
children: [ children: [
Widget.Box({ Widget.Box({
hpack: "start", hpack: 'start',
children: [ children: [
Widget.Label({ Widget.Label({
wrap: true, wrap: true,
class_name: audio.microphone class_name: audio.microphone
.bind("description") .bind('description')
.as((v) => .as((v) =>
device.description === v device.description === v
? "menu-button-icon active input txt-icon" ? 'menu-button-icon active input txt-icon'
: "menu-button-icon input txt-icon", : 'menu-button-icon input txt-icon',
), ),
label: "", label: '',
}), }),
Widget.Label({ Widget.Label({
truncate: "end", truncate: 'end',
wrap: true, wrap: true,
class_name: audio.microphone class_name: audio.microphone
.bind("description") .bind('description')
.as((v) => .as((v) =>
device.description === v device.description === v
? "menu-button-name active input" ? 'menu-button-name active input'
: "menu-button-name input", : 'menu-button-name input',
), ),
label: device.description, label: device.description,
}), }),

View File

@@ -1,16 +1,17 @@
const audio = await Service.import("audio"); const audio = await Service.import('audio');
import { Stream } from "types/service/audio"; import { PlaybackDevices } from 'lib/types/audio';
import { Stream } from 'types/service/audio';
const renderPlaybacks = (playbackDevices: Stream[]) => { const renderPlaybacks = (playbackDevices: Stream[]): PlaybackDevices => {
return playbackDevices.map((device) => { return playbackDevices.map((device) => {
if (device.description === "Dummy Output") { if (device.description === 'Dummy Output') {
return Widget.Box({ return Widget.Box({
class_name: "menu-unfound-button playback", class_name: 'menu-unfound-button playback',
child: Widget.Box({ child: Widget.Box({
children: [ children: [
Widget.Label({ Widget.Label({
class_name: "menu-button-name playback", class_name: 'menu-button-name playback',
label: "No playback devices found...", label: 'No playback devices found...',
}), }),
], ],
}), }),
@@ -22,29 +23,29 @@ const renderPlaybacks = (playbackDevices: Stream[]) => {
child: Widget.Box({ child: Widget.Box({
children: [ children: [
Widget.Box({ Widget.Box({
hpack: "start", hpack: 'start',
children: [ children: [
Widget.Label({ Widget.Label({
truncate: "end", truncate: 'end',
wrap: true, wrap: true,
class_name: audio.speaker class_name: audio.speaker
.bind("description") .bind('description')
.as((v) => .as((v) =>
device.description === v device.description === v
? "menu-button-icon active playback txt-icon" ? 'menu-button-icon active playback txt-icon'
: "menu-button-icon playback txt-icon", : 'menu-button-icon playback txt-icon',
), ),
label: "", label: '',
}), }),
Widget.Label({ Widget.Label({
truncate: "end", truncate: 'end',
wrap: true, wrap: true,
class_name: audio.speaker class_name: audio.speaker
.bind("description") .bind('description')
.as((v) => .as((v) =>
device.description === v device.description === v
? "menu-button-name active playback" ? 'menu-button-name active playback'
: "menu-button-name playback", : 'menu-button-name playback',
), ),
label: device.description, label: device.description,
}), }),

View File

@@ -1,66 +1,63 @@
const audio = await Service.import("audio"); const audio = await Service.import('audio');
import { renderInputDevices } from "./InputDevices.js"; import { BoxWidget } from 'lib/types/widget.js';
import { renderPlaybacks } from "./PlaybackDevices.js"; import { renderInputDevices } from './InputDevices.js';
import { renderPlaybacks } from './PlaybackDevices.js';
const availableDevices = () => { const availableDevices = (): BoxWidget => {
return Widget.Box({ return Widget.Box({
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
class_name: "menu-section-container playback", class_name: 'menu-section-container playback',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
class_name: "menu-label-container playback", class_name: 'menu-label-container playback',
hpack: "fill", hpack: 'fill',
child: Widget.Label({ child: Widget.Label({
class_name: "menu-label audio playback", class_name: 'menu-label audio playback',
hexpand: true, hexpand: true,
hpack: "start", hpack: 'start',
label: "Playback Devices", label: 'Playback Devices',
}), }),
}), }),
Widget.Box({ Widget.Box({
class_name: "menu-items-section playback", class_name: 'menu-items-section playback',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
class_name: "menu-container playback", class_name: 'menu-container playback',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
children: audio children: audio.bind('speakers').as((v) => renderPlaybacks(v)),
.bind("speakers")
.as((v) => renderPlaybacks(v)),
}), }),
], ],
}), }),
], ],
}), }),
Widget.Box({ Widget.Box({
class_name: "menu-label-container input", class_name: 'menu-label-container input',
hpack: "fill", hpack: 'fill',
child: Widget.Label({ child: Widget.Label({
class_name: "menu-label audio input", class_name: 'menu-label audio input',
hexpand: true, hexpand: true,
hpack: "start", hpack: 'start',
label: "Input Devices", label: 'Input Devices',
}), }),
}), }),
Widget.Box({ Widget.Box({
class_name: "menu-items-section input", class_name: 'menu-items-section input',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
class_name: "menu-container input", class_name: 'menu-container input',
vertical: true, vertical: true,
children: [ children: [
Widget.Box({ Widget.Box({
vertical: true, vertical: true,
children: audio children: audio.bind('microphones').as((v) => renderInputDevices(v)),
.bind("microphones")
.as((v) => renderInputDevices(v)),
}), }),
], ],
}), }),

View File

@@ -1,24 +1,23 @@
import DropdownMenu from "../DropdownMenu.js"; import Window from 'types/widgets/window.js';
import { activeDevices } from "./active/index.js"; import DropdownMenu from '../DropdownMenu.js';
import { availableDevices } from "./available/index.js"; import { activeDevices } from './active/index.js';
import { availableDevices } from './available/index.js';
import { Attribute, Child } from 'lib/types/widget.js';
export default () => { export default (): Window<Child, Attribute> => {
return DropdownMenu({ return DropdownMenu({
name: "audiomenu", name: 'audiomenu',
transition: "crossfade", transition: 'crossfade',
child: Widget.Box({ child: Widget.Box({
class_name: "menu-items audio", class_name: 'menu-items audio',
hpack: "fill", hpack: 'fill',
hexpand: true, hexpand: true,
child: Widget.Box({ child: Widget.Box({
vertical: true, vertical: true,
hpack: "fill", hpack: 'fill',
hexpand: true, hexpand: true,
class_name: "menu-items-container audio", class_name: 'menu-items-container audio',
children: [ children: [activeDevices(), availableDevices()],
activeDevices(),
availableDevices(),
],
}), }),
}), }),
}); });

Some files were not shown because too many files have changed in this diff Show More