Merge branch 'master' into master

This commit is contained in:
orangc
2024-09-19 10:13:59 +03:00
committed by GitHub
241 changed files with 14704 additions and 8799 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

41
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: CI
on:
pull_request:
branches:
- master
jobs:
code_quality:
runs-on: ubuntu-latest
steps:
- name: Checkout main repository
uses: actions/checkout@v3
- name: Clone ags-types to temp dir
uses: actions/checkout@v3
with:
repository: Jas-SinghFSU/ags-types
path: temp-ags-types
- name: Copy types to types/
run: |
rm -rf types
mkdir -p types
cp -R temp-ags-types/types/* types/
rm -rf temp-ags-types
- name: Node Setup
uses: actions/setup-node@v3
with:
node-version: '21'
- name: Install Dependencies
run: npm install
- name: ESLint
run: npm run lint
- name: Type Check
run: npx tsc --noEmit --pretty --extendedDiagnostics

1
.gitignore vendored
View File

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

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "external/ags-types"]
path = external/ags-types
url = https://github.com/Jas-SinghFSU/ags-types.git

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
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Jas Singh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -11,21 +11,26 @@
<br/>
# HyprPanel 🚀
A panel built for Hyprland with [AGS](https://github.com/Aylur/ags)
![HyprPanel](./assets/HyprPanel.png)
## Installation
The [HyprPanel Wiki](https://hyprpanel.com/getting_started/installation.html) contains in depth instructions for installing the panel and all of its dependencies. The instructions below are general instructions for installing the panel.
## Requirements
Bun
```sh
curl -fsSL https://bun.sh/install | bash && \
sudo ln -s $HOME/.bun/bin/bun /usr/local/bin/bun
```
Additional dependencies:
```sh
pipewire
libgtop
@@ -45,53 +50,110 @@ gnome-bluetooth-3.0
```
Optional Dependencies:
```sh
## Used for Tracking GPU Usage in your Dashboard (NVidia only)
python
python-gpustat
## Only if a pywal hook from wallpaper changes applied through settings is desired
pywal
## To check for pacman updates in the default script used in the updates module
pacman-contrib
## To switch between power profiles in battery module
power-profiles-daemon
```
Arch (pacman):
### Arch
pacman:
```bash
sudo pacman -S pipewire libgtop bluez bluez-utils btop networkmanager dart-sass wl-clipboard brightnessctl swww python gnome-bluetooth-3.0
sudo pacman -S pipewire libgtop bluez bluez-utils btop networkmanager dart-sass wl-clipboard brightnessctl swww python gnome-bluetooth-3.0 pacman-contrib power-profiles-daemon
```
Arch (AUR):
AUR:
```bash
yay -S grimblast-git gpu-screen-recorder hyprpicker matugen-bin python-gpustat aylurs-gtk-shell-git
```
### Fedora
COPR - Add [solopasha/hyprland](https://copr.fedorainfracloud.org/coprs/solopasha/hyprland/) for most hyprland-related dependencies, and [hues-sueh/packages](https://copr.fedorainfracloud.org/coprs/heus-sueh/packages/) for matugen. Both provide the `swww` package, so prioritise the former repo:
```bash
sudo dnf copr enable solopasha/hyprland
sudo dnf copr enable heus-sueh/packages
sudo dnf config-manager --save --setopt=copr:copr.fedorainfracloud.org:heus-sueh:packages.priority=200
```
DNF:
```bash
sudo dnf install pipewire libgtop2 bluez bluez-tools grimblast hyprpicker btop NetworkManager wl-clipboard swww brightnessctl gnome-bluetooth aylurs-gtk-shell power-profiles-daemon gvfs
```
bun:
```bash
bun install -g sass
```
flatpak:
```bash
flatpak install flathub --system com.dec05eba.gpu_screen_recorder
```
#### Optional Dependencies
pip:
```bash
sudo dnf install python python3-pip; pip install gpustat pywal
```
### NixOS
For NixOS/Home-Manager, see [NixOS & Home-Manager instructions](#nixos--home-manager).
## Instructions
### AGS
Once everything is installed you need to put the contents of this repo in `~/.config/ags`.
If you already have something in `~/.config/ags`, it's recommended that you back it up with:
```bash
mv $HOME/.config/ags $HOME/.config/ags.bkup
```
Otherwise you can use this command to install the panel:
```bash
git clone https://github.com/Jas-SinghFSU/HyprPanel.git && \
ln -s $(pwd)/HyprPanel $HOME/.config/ags
```
### Nerd Fonts
Additionally, you need to ensure that you have a [Nerd Font](https://www.nerdfonts.com/font-downloads) installed for your icons to render properly.
### Launch the panel
Afterwards you can run the panel with the following command in your terminal:
```bash
ags
```
Or you can add it to your Hyprland config (hyprland.conf) to auto-start with:
```bash
exec-once = ags
```
### NixOS & Home-Manager
Alternatively, if you're using NixOS and/or Home-Manager, you can setup AGS using the provided Nix Flake. First, add the repository to your Flake's inputs, and enable the overlay.
```nix
# flake.nix
@@ -169,6 +231,7 @@ The panel is automatically scaled based on your font size in `Configuration > Ge
### Specifying bar layouts per monitor
To specify layouts for each monitor you can create a JSON object such as:
```JSON
{
"0": {
@@ -223,19 +286,21 @@ To specify layouts for each monitor you can create a JSON object such as:
```
Where each monitor is defined by its index (0, 1, 2 in this case) and each section (left, middle, right) contains one or more of the following modules:
```js
"battery"
"dashboard"
"workspaces"
"windowtitle"
"media"
"notifications"
"volume"
"network"
"bluetooth"
"clock"
"systray"
'battery';
'dashboard';
'workspaces';
'windowtitle';
'media';
'notifications';
'volume';
'network';
'bluetooth';
'clock';
'systray';
```
Since the text-box in the options dialog isn't sufficient, it is recommended that you create this JSON configuration in a text editor elsewhere and paste it into the layout text-box under Configuration > Bar > "Bar Layouts for Monitors".
### Additional Configuration

View File

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

View File

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

View File

@@ -2,8 +2,10 @@ import { Option } from 'widget/settings/shared/Option';
import { Header } from 'widget/settings/shared/Header';
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({
vscroll: 'automatic',
hscroll: 'automatic',
@@ -204,17 +206,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.netstat.icon,
title: 'Netstat Icon',
type: 'enum',
enums: [
'󰖟',
'󰇚',
'󰕒',
'󰛳',
'',
'󰣺',
'󰖩',
'',
'󰈀',
],
enums: ['󰖟', '󰇚', '󰕒', '󰛳', '', '󰣺', '󰖩', '', '󰈀'],
}),
Option({
opt: options.bar.customModules.netstat.label,
@@ -336,17 +328,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.updates.icon,
title: 'Updates Icon',
type: 'enum',
enums: [
'󰚰',
'󰇚',
'',
'󱑢',
'󱑣',
'󰏖',
'',
'󰏔',
'󰏗',
],
enums: ['󰚰', '󰇚', '', '󱑢', '󱑣', '󰏖', '', '󰏔', '󰏗'],
}),
Option({
opt: options.bar.customModules.updates.label,
@@ -367,7 +349,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.updates.pollingInterval,
title: 'Polling Interval',
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,
max: 60 * 24 * 1000,
increment: 1000,
@@ -491,4 +473,3 @@ export const CustomModuleSettings = () =>
],
}),
});

View File

@@ -1,12 +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';
const defaultCpuData: number = 0;
let previousCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(previousCpuData);
export const computeCPU = () => {
export const computeCPU = (): number => {
const currentCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(currentCpuData);
@@ -19,5 +17,4 @@ export const computeCPU = () => {
previousCpuData = currentCpuData;
return cpuUsagePercentage;
}
};

View File

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

View File

@@ -1,12 +1,18 @@
import { HyprctlDeviceLayout, HyprctlKeyboard, KbLabelType } from "lib/types/customModules/kbLayout";
import { layoutMap } from "./layouts";
import {
HyprctlDeviceLayout,
HyprctlKeyboard,
KbLabelType,
LayoutKeys,
LayoutValues,
} from 'lib/types/customModules/kbLayout';
import { layoutMap } from './layouts';
export const getKeyboardLayout = (obj: string, format: KbLabelType) => {
let hyprctlDevices: HyprctlDeviceLayout = JSON.parse(obj);
let keyboards = hyprctlDevices['keyboards'];
export const getKeyboardLayout = (obj: string, format: KbLabelType): LayoutKeys | LayoutValues => {
const hyprctlDevices: HyprctlDeviceLayout = JSON.parse(obj);
const keyboards = hyprctlDevices['keyboards'];
if (keyboards.length === 0) {
return "No KB!"
return format === 'code' ? 'Unknown' : 'Unknown Layout';
}
let mainKb = keyboards.find((kb: HyprctlKeyboard) => kb.main);
@@ -15,7 +21,8 @@ export const getKeyboardLayout = (obj: string, format: KbLabelType) => {
mainKb = keyboards[keyboards.length - 1];
}
let layout = mainKb['active_keymap'];
const layout: LayoutKeys = mainKb['active_keymap'] as LayoutKeys;
const foundLayout: LayoutValues = layoutMap[layout];
return format === "code" ? layoutMap[layout] || 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 { module } from "../module"
import options from 'options';
import { module } from '../module';
import { inputHandler } from "customModules/utils";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0";
import Button from "types/widgets/button";
import Label from "types/widgets/label";
import { getKeyboardLayout } from "./getLayout";
import { inputHandler } from 'customModules/utils';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
import { getKeyboardLayout } from './getLayout';
import { Module } from 'lib/types/bar';
const {
label,
labelType,
icon,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.kbLayout;
const { label, labelType, icon, leftClick, rightClick, middleClick, scrollUp, scrollDown } =
options.bar.customModules.kbLayout;
export const KbInput = () => {
export const KbInput = (): Module => {
const keyboardModule = module({
textIcon: icon.bind("value"),
tooltipText: "",
textIcon: icon.bind('value'),
tooltipText: '',
labelHook: (self: Label<Gtk.Widget>): void => {
self.hook(hyprland, () => {
self.hook(
hyprland,
() => {
Utils.execAsync('hyprctl devices -j')
.then((obj) => {
self.label = getKeyboardLayout(obj, labelType.value);
})
.catch((err) => { console.error(err); });
}, "keyboard-layout");
.catch((err) => {
console.error(err);
});
},
'keyboard-layout',
);
self.hook(labelType, () => {
Utils.execAsync('hyprctl devices -j')
.then((obj) => {
self.label = getKeyboardLayout(obj, labelType.value);
})
.catch((err) => { console.error(err); });
.catch((err) => {
console.error(err);
});
});
},
boxClass: "kblayout",
showLabelBinding: label.bind("value"),
boxClass: 'kblayout',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, {
@@ -68,6 +69,4 @@ export const KbInput = () => {
});
return keyboardModule;
}
};

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -71,7 +71,11 @@ const getNetworkUsage = (interfaceName: string = ''): NetworkUsage => {
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 interfaceName = interfaceNameVar ? interfaceNameVar.value : '';

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
const GLib = imports.gi.GLib;
import { divide } from 'customModules/utils';
import { GenericResourceData } from 'lib/types/customModules/generic';
import { Variable as VariableType } from 'types/variable';
export const calculateRamUsage = (round: VariableType<boolean>) => {
export const calculateRamUsage = (round: VariableType<boolean>): GenericResourceData => {
try {
const [success, meminfoBytes] = GLib.file_get_contents('/proc/meminfo');
@@ -26,17 +27,14 @@ export const calculateRamUsage = (round: VariableType<boolean>) => {
let usedRam = totalRamInBytes - availableRamInBytes;
usedRam = isNaN(usedRam) || usedRam < 0 ? 0 : usedRam;
return {
percentage: divide([totalRamInBytes, usedRam], round.value),
total: totalRamInBytes,
used: usedRam,
free: availableRamInBytes,
};
} catch (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
import { module } from "../module"
import { module } from '../module';
// Types
import { GenericResourceData } from "lib/types/customModules/generic";
import Button from "types/widgets/button";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0";
import { GenericResourceData } from 'lib/types/customModules/generic';
import Button from 'types/widgets/button';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
// Helper Methods
import { calculateRamUsage } from "./computeRam";
import { calculateRamUsage } from './computeRam';
// Utility Methods
import { formatTooltip, inputHandler, renderResourceLabel } from "customModules/utils";
import { ResourceLabelType } from "lib/types/bar";
import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils';
import { Module, ResourceLabelType } from 'lib/types/bar';
// Global Constants
import { LABEL_TYPES } from "lib/types/defaults/bar";
import { pollVariable } from "customModules/PollVar";
import { LABEL_TYPES } from 'lib/types/defaults/bar';
import { pollVariable } from 'customModules/PollVar';
// All the user configurable options for the ram module that are needed
const {
label,
labelType,
round,
leftClick,
rightClick,
middleClick,
pollingInterval
} = options.bar.customModules.ram;
const { label, labelType, round, leftClick, rightClick, middleClick, pollingInterval } = options.bar.customModules.ram;
const defaultRamData: GenericResourceData = { total: 0, used: 0, percentage: 0, free: 0 };
const ramUsage = Variable(defaultRamData);
const ramUsage = Variable<GenericResourceData>(defaultRamData);
pollVariable(
ramUsage,
[round.bind('value')],
pollingInterval.bind('value'),
calculateRamUsage,
round,
);
export const Ram = () => {
pollVariable(ramUsage, [round.bind('value')], pollingInterval.bind('value'), calculateRamUsage, round);
export const Ram = (): Module => {
const ramModule = module({
textIcon: "",
textIcon: '',
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) => {
const returnValue = renderResourceLabel(lblTyp, rmUsg, round);
return returnValue;
}),
tooltipText: labelType.bind("value").as(lblTyp => {
},
),
tooltipText: labelType.bind('value').as((lblTyp) => {
return formatTooltip('RAM', lblTyp);
}),
boxClass: "ram",
showLabelBinding: label.bind("value"),
boxClass: 'ram',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, {
@@ -71,18 +57,22 @@ export const Ram = () => {
},
onScrollUp: {
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: {
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;
}
};

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 { divide } from 'customModules/utils';
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>) => {
export const computeStorage = (round: VariableType<boolean>): GenericResourceData => {
try {
const currentFsUsage = new GTop.glibtop_fsusage();
GTop.glibtop_get_fsusage(currentFsUsage, "/");
GTop.glibtop_get_fsusage(currentFsUsage, '/');
const total = currentFsUsage.blocks * currentFsUsage.block_size;
const available = currentFsUsage.bavail * currentFsUsage.block_size;
const used = total - available;
previousFsUsage = currentFsUsage;
return {
total,
used,
@@ -26,7 +23,6 @@ export const computeStorage = (round: VariableType<boolean>) => {
};
} catch (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 { module } from "../module"
import options from 'options';
import { module } from '../module';
import { formatTooltip, inputHandler, renderResourceLabel } from "customModules/utils";
import { computeStorage } from "./computeStorage";
import { ResourceLabelType } from "lib/types/bar";
import { GenericResourceData } from "lib/types/customModules/generic";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0";
import Button from "types/widgets/button";
import { LABEL_TYPES } from "lib/types/defaults/bar";
import { pollVariable } from "customModules/PollVar";
import { formatTooltip, inputHandler, renderResourceLabel } from 'customModules/utils';
import { computeStorage } from './computeStorage';
import { Module, ResourceLabelType } from 'lib/types/bar';
import { GenericResourceData } from 'lib/types/customModules/generic';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from 'types/widgets/button';
import { LABEL_TYPES } from 'lib/types/defaults/bar';
import { pollVariable } from 'customModules/PollVar';
const {
label,
labelType,
icon,
round,
leftClick,
rightClick,
middleClick,
pollingInterval
} = options.bar.customModules.storage;
const { label, labelType, icon, round, leftClick, rightClick, middleClick, pollingInterval } =
options.bar.customModules.storage;
const defaultStorageData = { total: 0, used: 0, percentage: 0, free: 0 };
const storageUsage = Variable(defaultStorageData);
const storageUsage = Variable<GenericResourceData>(defaultStorageData);
pollVariable(
storageUsage,
[round.bind('value')],
pollingInterval.bind('value'),
computeStorage,
round,
);
pollVariable(storageUsage, [round.bind('value')], pollingInterval.bind('value'), computeStorage, round);
export const Storage = () => {
export const Storage = (): Module => {
const storageModule = module({
textIcon: icon.bind("value"),
textIcon: icon.bind('value'),
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) => {
return renderResourceLabel(lblTyp, storage, round);
}),
tooltipText: labelType.bind("value").as(lblTyp => {
},
),
tooltipText: labelType.bind('value').as((lblTyp) => {
return formatTooltip('Storage', lblTyp);
}),
boxClass: "storage",
showLabelBinding: label.bind("value"),
boxClass: 'storage',
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, {
@@ -61,18 +47,22 @@ export const Storage = () => {
},
onScrollUp: {
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: {
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;
}
};

View File

@@ -1,106 +1,146 @@
import { Option } from "widget/settings/shared/Option";
import { Header } from "widget/settings/shared/Header";
import { Option } from 'widget/settings/shared/Option';
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({
vscroll: "automatic",
hscroll: "automatic",
class_name: "menu-theme-page customModules paged-container",
vscroll: 'automatic',
hscroll: 'automatic',
class_name: 'menu-theme-page customModules paged-container',
child: Widget.Box({
class_name: "bar-theme-page paged-container",
class_name: 'bar-theme-page paged-container',
vertical: true,
children: [
Header('RAM'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.ram.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.ram.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('CPU'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.cpu.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.cpu.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('Storage'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.storage.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.storage.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('Netstat'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.netstat.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.netstat.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('Keyboard Layout'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.kbLayout.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.kbLayout.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('Updates'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.updates.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.updates.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('Weather'),
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.background, title: 'Label Background', type: 'color' }),
Option({
opt: options.theme.bar.buttons.modules.weather.background,
title: 'Label Background',
type: 'color',
}),
Option({
opt: options.theme.bar.buttons.modules.weather.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"Applies a background color to the icon section of the button.\nRequires 'split' button styling.",
type: 'color',
}),
Header('Power'),
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({
opt: options.theme.bar.buttons.modules.power.icon_background,
title: 'Icon Background',
subtitle: 'Applies a background color to the icon section of the button.\nRequires \'split\' button styling.',
type: 'color'
subtitle:
"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 { module } from "../module"
import options from 'options';
import { module } from '../module';
import { inputHandler } from "customModules/utils";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0";
import Button from "types/widgets/button";
import { Variable as VariableType } from "types/variable";
import { pollVariableBash } from "customModules/PollVar";
import { inputHandler } from 'customModules/utils';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from 'types/widgets/button';
import { Variable as VariableType } from 'types/variable';
import { pollVariableBash } from 'customModules/PollVar';
import { Module } from 'lib/types/bar';
const {
updateCommand,
@@ -20,12 +21,12 @@ const {
scrollDown,
} = 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;
return `${updateCount.padStart(2, '0')}`;
}
};
pollVariableBash(
pendingUpdates,
@@ -35,13 +36,13 @@ pollVariableBash(
processUpdateCount,
);
export const Updates = () => {
export const Updates = (): Module => {
const updatesModule = module({
textIcon: icon.bind("value"),
tooltipText: pendingUpdates.bind("value").as(v => `${v} updates available`),
boxClass: "updates",
label: pendingUpdates.bind("value"),
showLabelBinding: label.bind("value"),
textIcon: icon.bind('value'),
tooltipText: pendingUpdates.bind('value').as((v) => `${v} updates available`),
boxClass: 'updates',
label: pendingUpdates.bind('value'),
showLabelBinding: label.bind('value'),
props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, {
@@ -66,7 +67,4 @@ export const Updates = () => {
});
return updatesModule;
}
};

View File

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

View File

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

View File

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

1
external/ags-types vendored Submodule

Submodule external/ags-types added at 87b5046791

14
globals.d.ts vendored Normal file
View File

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

View File

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

15
globals/network.ts Normal file
View File

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

37
globals/notification.ts Normal file
View File

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

View File

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

13
globals/variables.ts Normal file
View File

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

View File

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

10
globals/window.ts Normal file
View File

@@ -0,0 +1,10 @@
export const WINDOW_LAYOUTS: string[] = [
'center',
'top',
'top-right',
'top-center',
'top-left',
'bottom-left',
'bottom-center',
'bottom-right',
];

142
lib/constants/colors.ts Normal file
View File

@@ -0,0 +1,142 @@
export const namedColors = new Set([
'alice blue',
'antique white',
'aqua',
'aquamarine',
'azure',
'beige',
'bisque',
'black',
'blanched almond',
'blue',
'blue violet',
'brown',
'burlywood',
'cadet blue',
'chartreuse',
'chocolate',
'coral',
'cornflower blue',
'cornsilk',
'crimson',
'cyan',
'dark blue',
'dark cyan',
'dark goldenrod',
'dark gray',
'dark green',
'dark khaki',
'dark magenta',
'dark olive green',
'dark orange',
'dark orchid',
'dark red',
'dark salmon',
'dark sea green',
'dark slate blue',
'dark slate gray',
'dark turquoise',
'dark violet',
'deep pink',
'deep sky blue',
'dim gray',
'dodger blue',
'firebrick',
'floral white',
'forest green',
'fuchsia',
'gainsboro',
'ghost white',
'gold',
'goldenrod',
'gray',
'green',
'green yellow',
'honeydew',
'hot pink',
'indian red',
'indigo',
'ivory',
'khaki',
'lavender',
'lavender blush',
'lawn green',
'lemon chiffon',
'light blue',
'light coral',
'light cyan',
'light goldenrod yellow',
'light green',
'light grey',
'light pink',
'light salmon',
'light sea green',
'light sky blue',
'light slate gray',
'light steel blue',
'light yellow',
'lime',
'lime green',
'linen',
'magenta',
'maroon',
'medium aquamarine',
'medium blue',
'medium orchid',
'medium purple',
'medium sea green',
'medium slate blue',
'medium spring green',
'medium turquoise',
'medium violet red',
'midnight blue',
'mint cream',
'misty rose',
'moccasin',
'navajo white',
'navy',
'old lace',
'olive',
'olive drab',
'orange',
'orange red',
'orchid',
'pale goldenrod',
'pale green',
'pale turquoise',
'pale violet red',
'papaya whip',
'peach puff',
'peru',
'pink',
'plum',
'powder blue',
'purple',
'red',
'rosy brown',
'royal blue',
'saddle brown',
'salmon',
'sandy brown',
'sea green',
'seashell',
'sienna',
'silver',
'sky blue',
'slate blue',
'slate gray',
'snow',
'spring green',
'steel blue',
'tan',
'teal',
'thistle',
'tomato',
'turquoise',
'violet',
'wheat',
'white',
'white smoke',
'yellow',
'yellow green',
]);

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
import { Notification } from 'types/service/notifications';
export const filterNotifications = (notifications: Notification[], filter: string[]): Notification[] => {
const notifFilter = new Set(filter.map((name: string) => name.toLowerCase().replace(/\s+/g, '_')));
const filteredNotifications = notifications.filter((notif: Notification) => {
const normalizedAppName = notif.app_name.toLowerCase().replace(/\s+/g, '_');
return !notifFilter.has(normalizedAppName);
});
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 { Variable } from "types/variable"
import Box from "types/widgets/box";
import Label from "types/widgets/label";
import { Widget as WidgetType } from "types/widgets/widget"
import { Binding, Connectable } from 'types/service';
import { Variable } from 'types/variable';
import Box from 'types/widgets/box';
import Button from 'types/widgets/button';
import Label from 'types/widgets/label';
import { Attribute, Child } from './widget';
export type Child = {
export type BarBoxChild = {
component: Box<Gtk.Widget, unknown>;
isVisible?: boolean;
isVis?: Variable<boolean>;
boxClass: string;
props: ButtonProps;
};
} & ButtonProps;
export type SelfButton = Button<Child, Attribute>;
export type BoxHook = (self: Box<Gtk.Widget, Gtk.Widget>) => void;
export type LabelHook = (self: Label<Gtk.Widget>) => void;
export type Module = {
icon?: string | Binding<string>,
textIcon?: string | Binding<string>,
label?: string | Binding<string>,
labelHook?: LabelHook,
boundLabel?: string,
tooltipText?: string | Binding<string>,
boxClass: string,
props?: ButtonProps,
showLabel?: boolean,
showLabelBinding?: Binding,
hook?: BoxHook,
connection?: Binding<Connectable>
}
icon?: string | Binding<string>;
textIcon?: string | Binding<string>;
label?: string | Binding<string>;
labelHook?: LabelHook;
boundLabel?: string;
tooltipText?: string | Binding<string>;
boxClass: string;
props?: ButtonProps;
showLabel?: boolean;
showLabelBinding?: Binding;
hook?: BoxHook;
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 NetstatLabelType = "full" | "in" | "out";
export type RateUnit = "GiB" | "MiB" | "KiB" | "auto";
export type NetstatIcon = '󰖟' | '󰇚' | '󰕒' | '󰛳' | '' | '󰣺' | '󰖩' | '' | '󰈀';
export type NetstatLabelType = 'full' | 'in' | 'out';
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;
used: number;
free: number;
percentage: number;
}
};
type GenericResourceData = ResourceUsage & {
free: number;
};
export type Postfix = 'TiB' | 'GiB' | 'MiB' | 'KiB' | 'B';

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

11
lib/types/dropdownmenu.d.ts vendored Normal file
View File

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

3
lib/types/filechooser.d.ts vendored Normal file
View File

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

View File

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

2
lib/types/mpris.d.ts vendored Normal file
View File

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

View File

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

View File

@@ -1,3 +1,5 @@
import icons from 'modules/icons/index';
export interface NotificationArgs {
appName?: string;
body?: string;
@@ -9,3 +11,5 @@ export interface NotificationArgs {
timeout?: number;
transient?: boolean;
}
export type NotificationIcon = keyof typeof icons.notifications;

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

@@ -1,119 +1,215 @@
import { Opt } from "lib/option";
import { Variable } from "types/variable";
import { Opt } from 'lib/option';
import { Variable } from 'types/variable';
import { defaultColorMap } from './defaults/options';
export type Unit = "imperial" | "metric";
export type PowerOptions = "sleep" | "reboot" | "logout" | "shutdown";
export type NotificationAnchor = "top" | "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 MkOptionsResult<T> = {
configFile: string;
array: () => Opt[];
reset: () => Promise<string>;
resetTheme: () => Promise<string>;
handler: (deps: string[], callback: () => void) => void;
};
export type RecursiveOptionsObject = {
[key: string]: RecursiveOptionsObject | Opt<string | number | boolean> | Opt<any>;
};
export type BarLocation = 'top' | 'bottom';
export type Unit = 'imperial' | 'metric';
export type PowerOptions = 'sleep' | 'reboot' | 'logout' | 'shutdown';
export type NotificationAnchor =
| 'top'
| '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 = {
filePath: string,
themeOnly: boolean
}
filePath: string;
themeOnly: boolean;
};
export type RowProps<T> = {
opt: Opt<T>
title: string
note?: string
opt: Opt<T>;
title: string;
note?: string;
type?:
| "number"
| "color"
| "float"
| "object"
| "string"
| "enum"
| "boolean"
| "img"
| "wallpaper"
| "export"
| "import"
| "config_import"
| "font"
enums?: string[]
max?: number
min?: number
disabledBinding?: Variable<boolean>
exportData?: ThemeExportData
subtitle?: string | VarType<any> | Opt,
subtitleLink?: string,
dependencies?: string[],
increment?: number
}
| 'number'
| 'color'
| 'float'
| 'object'
| 'string'
| 'enum'
| 'boolean'
| 'img'
| 'wallpaper'
| 'export'
| 'import'
| 'config_import'
| 'font';
enums?: T[];
max?: number;
min?: number;
disabledBinding?: Variable<boolean>;
exportData?: ThemeExportData;
subtitle?: string | VarType<any> | Opt;
subtitleLink?: string;
dependencies?: string[];
increment?: number;
};
export type OSDOrientation = "horizontal" | "vertical";
export type OSDOrientation = 'horizontal' | 'vertical';
export type HexColor = `#${string}`;
export type WindowLayer = 'top' | 'bottom' | 'overlay' | 'background';
export type ActiveWsIndicator = 'underline' | 'highlight' | 'color';
export type MatugenColors = {
"background": HexColor,
"error": HexColor,
"error_container": HexColor,
"inverse_on_surface": HexColor,
"inverse_primary": HexColor,
"inverse_surface": HexColor,
"on_background": HexColor,
"on_error": HexColor,
"on_error_container": HexColor,
"on_primary": HexColor,
"on_primary_container": HexColor,
"on_primary_fixed": HexColor,
"on_primary_fixed_variant": HexColor,
"on_secondary": HexColor,
"on_secondary_container": HexColor,
"on_secondary_fixed": HexColor,
"on_secondary_fixed_variant": HexColor,
"on_surface": HexColor,
"on_surface_variant": HexColor,
"on_tertiary": HexColor,
"on_tertiary_container": HexColor,
"on_tertiary_fixed": HexColor,
"on_tertiary_fixed_variant": HexColor,
"outline": HexColor,
"outline_variant": HexColor,
"primary": HexColor,
"primary_container": HexColor,
"primary_fixed": HexColor,
"primary_fixed_dim": HexColor,
"scrim": HexColor,
"secondary": HexColor,
"secondary_container": HexColor,
"secondary_fixed": HexColor,
"secondary_fixed_dim": HexColor,
"shadow": HexColor,
"surface": HexColor,
"surface_bright": HexColor,
"surface_container": HexColor,
"surface_container_high": HexColor,
"surface_container_highest": HexColor,
"surface_container_low": HexColor,
"surface_container_lowest": HexColor,
"surface_dim": HexColor,
"surface_variant": HexColor,
"tertiary": HexColor,
"tertiary_container": HexColor,
"tertiary_fixed": HexColor,
"tertiary_fixed_dim": HexColor
}
background: HexColor;
error: HexColor;
error_container: HexColor;
inverse_on_surface: HexColor;
inverse_primary: HexColor;
inverse_surface: HexColor;
on_background: HexColor;
on_error: HexColor;
on_error_container: HexColor;
on_primary: HexColor;
on_primary_container: HexColor;
on_primary_fixed: HexColor;
on_primary_fixed_variant: HexColor;
on_secondary: HexColor;
on_secondary_container: HexColor;
on_secondary_fixed: HexColor;
on_secondary_fixed_variant: HexColor;
on_surface: HexColor;
on_surface_variant: HexColor;
on_tertiary: HexColor;
on_tertiary_container: HexColor;
on_tertiary_fixed: HexColor;
on_tertiary_fixed_variant: HexColor;
outline: HexColor;
outline_variant: HexColor;
primary: HexColor;
primary_container: HexColor;
primary_fixed: HexColor;
primary_fixed_dim: HexColor;
scrim: HexColor;
secondary: HexColor;
secondary_container: HexColor;
secondary_fixed: HexColor;
secondary_fixed_dim: HexColor;
shadow: HexColor;
surface: HexColor;
surface_bright: HexColor;
surface_container: HexColor;
surface_container_high: HexColor;
surface_container_highest: HexColor;
surface_container_low: HexColor;
surface_container_lowest: HexColor;
surface_dim: HexColor;
surface_variant: HexColor;
tertiary: HexColor;
tertiary_container: HexColor;
tertiary_fixed: HexColor;
tertiary_fixed_dim: HexColor;
};
type MatugenScheme =
| "content"
| "expressive"
| "fidelity"
| "fruit-salad"
| "monochrome"
| "neutral"
| "rainbow"
| "tonal-spot";
export type MatugenVariation = {
rosewater: HexColor;
flamingo: HexColor;
pink: HexColor;
mauve: HexColor;
red: HexColor;
maroon: HexColor;
peach: HexColor;
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 =
| "standard_1"
| "standard_2"
| "standard_3"
| "monochrome_1"
| "monochrome_2"
| "monochrome_3"
| "vivid_1"
| "vivid_2"
| "vivid_3"
export type MatugenVariations =
| 'standard_1'
| 'standard_2'
| 'standard_3'
| 'monochrome_1'
| 'monochrome_2'
| 'monochrome_3'
| 'vivid_1'
| 'vivid_2'
| 'vivid_3';
type MatugenTheme = "light" | "dark";
type MatugenTheme = 'light' | 'dark';
export type ColorMapKey = keyof typeof defaultColorMap;
export type ColorMapValue = (typeof defaultColorMap)[ColorMapKey];

35
lib/types/popupwindow.d.ts vendored Normal file
View File

@@ -0,0 +1,35 @@
import { Widget } from 'types/widgets/widget';
import { WindowProps } from 'types/widgets/window';
import { Transition } from './widget';
export type PopupWindowProps = {
name: string;
child: any;
layout?: Layouts;
transition?: any;
exclusivity?: Exclusivity;
} & WindowProps;
export type LayoutFunction = (
name: string,
child: Widget,
transition: Transition,
) => {
center: () => Widget;
top: () => Widget;
'top-right': () => Widget;
'top-center': () => Widget;
'top-left': () => Widget;
'bottom-left': () => Widget;
'bottom-center': () => Widget;
'bottom-right': () => Widget;
};
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';

8
lib/types/powerprofiles.d.ts vendored Normal file
View File

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

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

6
lib/types/utils.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
import { substitutes } from 'lib/icons';
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>;

3
lib/types/volume.d.ts vendored Normal file
View File

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

View File

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

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

@@ -1,3 +1,28 @@
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Box from 'types/widgets/box';
export type Exclusivity = 'normal' | 'ignore' | 'exclusive';
export type Anchor = "left" | "right" | "top" | "down";
export type Transition = "none" | "crossfade" | "slide_right" | "slide_left" | "slide_up" | "slide_down";
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,25 @@
export type WorkspaceRule = {
workspaceString: string,
monitor: string,
}
workspaceString: string;
monitor: string;
};
export type WorkspaceMap = {
[key: string]: number[],
}
[key: string]: number[];
};
export type MonitorMap = {
[key: number]: string;
};
export type WorkspaceIcons = {
[key: string]: string;
};
export type WorkspaceIconsColored = {
[key: string]: {
color: string;
icon: string;
};
};
export type WorkspaceIconMap = WorkspaceIcons | WorkspaceIconsColored;

View File

@@ -1,59 +1,65 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type Application } from "types/service/applications"
import { NotificationAnchor } from "./types/options"
import { OSDAnchor } from "lib/types/options";
import icons, { substitutes } from "./icons"
import Gtk from "gi://Gtk?version=3.0"
import Gdk from "gi://Gdk"
import GLib from "gi://GLib?version=2.0"
import GdkPixbuf from "gi://GdkPixbuf";
import { NotificationArgs } from "types/utils/notify"
import { type Application } from 'types/service/applications';
import { NotificationAnchor } from './types/options';
import { OSDAnchor } from 'lib/types/options';
import icons, { substitutes } from './icons';
import Gtk from 'gi://Gtk?version=3.0';
import Gdk from 'gi://Gdk';
import GLib from 'gi://GLib?version=2.0';
import GdkPixbuf from 'gi://GdkPixbuf';
import { NotificationArgs } from 'types/utils/notify';
import { SubstituteKeys } from './types/utils';
import { Window } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
import { namedColors } from './constants/colors';
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
*/
export function icon(name: string | null, fallback = icons.missing) {
if (!name)
return fallback || ""
export function icon(name: string | null, fallback = icons.missing): string {
const validateSubstitute = (name: string): name is SubstituteKeys => name in substitutes;
if (GLib.file_test(name, GLib.FileTest.EXISTS))
return name
if (!name) return fallback || '';
const icon = (substitutes[name] || name)
if (Utils.lookUpIcon(icon))
return icon
if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`)
return fallback
let icon: string = name;
if (validateSubstitute(name)) {
icon = substitutes[name];
}
if (Utils.lookUpIcon(icon)) return icon;
print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`);
return fallback;
}
/**
* @returns execAsync(["bash", "-c", cmd])
*/
export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]) {
const cmd = typeof strings === "string" ? strings : strings
.flatMap((str, i) => str + `${values[i] ?? ""}`)
.join("")
export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]): Promise<string> {
const cmd =
typeof strings === 'string' ? strings : strings.flatMap((str, i) => str + `${values[i] ?? ''}`).join('');
return Utils.execAsync(["bash", "-c", cmd]).catch(err => {
console.error(cmd, err)
return ""
})
return Utils.execAsync(['bash', '-c', cmd]).catch((err) => {
console.error(cmd, err);
return '';
});
}
/**
* @returns execAsync(cmd)
*/
export async function sh(cmd: string | string[]) {
return Utils.execAsync(cmd).catch(err => {
console.error(typeof cmd === "string" ? cmd : cmd.join(" "), err)
return ""
})
export async function sh(cmd: string | string[]): Promise<string> {
return Utils.execAsync(cmd).catch((err) => {
console.error(typeof cmd === 'string' ? cmd : cmd.join(' '), err);
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;
return range(n, 0).flatMap(widget);
}
@@ -61,64 +67,62 @@ export function forMonitors(widget: (monitor: number) => Gtk.Window) {
/**
* @returns [start...length]
*/
export function range(length: number, start = 1) {
return Array.from({ length }, (_, i) => i + start)
export function range(length: number, start = 1): number[] {
return Array.from({ length }, (_, i) => i + start);
}
/**
* @returns true if all of the `bins` are found
*/
export function dependencies(...bins: string[]) {
const missing = bins.filter(bin => Utils.exec({
export function dependencies(...bins: string[]): boolean {
const missing = bins.filter((bin) =>
Utils.exec({
cmd: `which ${bin}`,
out: () => false,
err: () => true,
}))
}),
);
if (missing.length > 0) {
console.warn(Error(`missing dependencies: ${missing.join(", ")}`))
console.warn(Error(`missing dependencies: ${missing.join(', ')}`));
Notify({
summary: "Dependencies not found!",
body: `The following dependencies are missing: ${missing.join(", ")}`,
summary: 'Dependencies not found!',
body: `The following dependencies are missing: ${missing.join(', ')}`,
iconName: icons.ui.warning,
timeout: 7000
timeout: 7000,
});
}
return missing.length === 0
return missing.length === 0;
}
/**
* run app detached
*/
export function launchApp(app: Application) {
export function launchApp(app: Application): void {
const exe = app.executable
.split(/\s+/)
.filter(str => !str.startsWith("%") && !str.startsWith("@"))
.join(" ")
.filter((str) => !str.startsWith('%') && !str.startsWith('@'))
.join(' ');
bash(`${exe} &`)
app.frequency += 1
bash(`${exe} &`);
app.frequency += 1;
}
/**
* 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
const cairo = imports.gi.cairo as any
const alloc = widget.get_allocation()
const surface = new cairo.ImageSurface(
cairo.Format.ARGB32,
alloc.width,
alloc.height,
)
const cr = new cairo.Context(surface)
cr.setSourceRGBA(255, 255, 255, 0)
cr.rectangle(0, 0, alloc.width, alloc.height)
cr.fill()
widget.draw(cr)
return surface
const cairo = imports.gi.cairo as any;
const alloc = widget.get_allocation();
const surface = new cairo.ImageSurface(cairo.Format.ARGB32, alloc.width, alloc.height);
const cr = new cairo.Context(surface);
cr.setSourceRGBA(255, 255, 255, 0);
cr.rectangle(0, 0, alloc.width, alloc.height);
cr.fill();
widget.draw(cr);
return surface;
}
/**
@@ -129,9 +133,10 @@ export const isAnImage = (imgFilePath: string): boolean => {
GdkPixbuf.Pixbuf.new_from_file(imgFilePath);
return true;
} catch (error) {
console.error(error);
return false;
}
}
};
export const Notify = (notifPayload: NotificationArgs): void => {
let command = 'notify-send';
@@ -145,20 +150,42 @@ export const Notify = (notifPayload: NotificationArgs): void => {
if (notifPayload.transient) command += ` -e`;
if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`;
Utils.execAsync(command)
}
export function getPosition(pos: NotificationAnchor | OSDAnchor): ("top" | "bottom" | "left" | "right")[] {
const positionMap: { [key: string]: ("top" | "bottom" | "left" | "right")[] } = {
"top": ["top"],
"top right": ["top", "right"],
"top left": ["top", "left"],
"bottom": ["bottom"],
"bottom right": ["bottom", "right"],
"bottom left": ["bottom", "left"],
"right": ["right"],
"left": ["left"],
Utils.execAsync(command);
};
return positionMap[pos] || ["top"];
export function getPosition(pos: NotificationAnchor | OSDAnchor): ('top' | 'bottom' | 'left' | 'right')[] {
const positionMap: { [key: string]: ('top' | 'bottom' | 'left' | 'right')[] } = {
top: ['top'],
'top right': ['top', 'right'],
'top left': ['top', 'left'],
bottom: ['bottom'],
'bottom right': ['bottom', 'right'],
'bottom left': ['bottom', 'left'],
right: ['right'],
left: ['left'],
};
return positionMap[pos] || ['top'];
}
export const isValidGjsColor = (color: string): boolean => {
const colorLower = color.toLowerCase().trim();
if (namedColors.has(colorLower)) {
return true;
}
const hexColorRegex = /^#(?:[a-fA-F0-9]{3,4}|[a-fA-F0-9]{6,8})$/;
const rgbRegex = /^rgb\(\s*(\d{1,3}%?\s*,\s*){2}\d{1,3}%?\s*\)$/;
const rgbaRegex = /^rgba\(\s*(\d{1,3}%?\s*,\s*){3}(0|1|0?\.\d+)\s*\)$/;
if (hexColorRegex.test(color)) {
return true;
}
if (rgbRegex.test(colorLower) || rgbaRegex.test(colorLower)) {
return true;
}
return false;
};

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(), {
poll: [1000, () => GLib.DateTime.new_now_local()],
})
poll: [1000, (): DateTime => GLib.DateTime.new_now_local()],
});
export const uptime = Variable(0, {
poll: [60_000, "cat /proc/uptime", line =>
Number.parseInt(line.split(".")[0]) / 60,
],
})
poll: [60_000, 'cat /proc/uptime', (line): number => Number.parseInt(line.split('.')[0]) / 60],
});
export const distro = {
id: GLib.get_os_info("ID"),
logo: GLib.get_os_info("LOGO"),
}
id: GLib.get_os_info('ID'),
logo: GLib.get_os_info('LOGO'),
};

30
main.ts
View File

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

View File

@@ -1,8 +1,10 @@
const hyprland = await Service.import("hyprland");
const hyprland = await Service.import('hyprland');
import {
Menu,
Workspaces, ClientTitle, Media,
Workspaces,
ClientTitle,
Media,
Notifications,
Volume,
Network,
@@ -20,121 +22,114 @@ import {
Updates,
Weather,
Power,
} from "./Exports"
} from './Exports';
import { BarItemBox as WidgetContainer } from "../shared/barItemBox.js";
import options from "options";
import Gdk from "gi://Gdk?version=3.0";
import Button from "types/widgets/button.js";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0.js";
import { BarItemBox as WidgetContainer } from '../shared/barItemBox.js';
import options from 'options';
import Gdk from 'gi://Gdk?version=3.0';
import Button from 'types/widgets/button.js';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0.js';
import './SideEffects';
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 { location } = options.theme.bar;
export type BarWidget = keyof typeof widget;
type Section = "battery"
| "dashboard"
| "workspaces"
| "windowtitle"
| "media"
| "notifications"
| "volume"
| "network"
| "bluetooth"
| "clock"
| "Ram"
| "Cpu"
| "Storage"
| "Netstat"
| "KbInput"
| "Updates"
| "Weather"
| "Power"
| "systray";
type Section =
| 'battery'
| 'dashboard'
| 'workspaces'
| 'windowtitle'
| 'media'
| 'notifications'
| 'volume'
| 'network'
| 'bluetooth'
| 'clock'
| 'ram'
| 'cpu'
| 'storage'
| 'netstat'
| 'kbinput'
| 'updates'
| 'weather'
| 'power'
| 'systray';
type Layout = {
left: Section[],
middle: Section[],
right: Section[],
}
left: Section[];
middle: Section[];
right: Section[];
};
type BarLayout = {
[key: string]: Layout
[key: string]: Layout;
};
const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
const matchingKey = Object.keys(layouts).find((key) => key === monitor.toString());
const wildcard = Object.keys(layouts).find((key) => key === '*');
if (matchingKey) {
return layouts[matchingKey];
}
const getModulesForMonitor = (monitor: number, curLayouts: BarLayout) => {
const foundMonitor = Object.keys(curLayouts).find(mon => mon === monitor.toString());
const defaultSetup: Layout = {
left: [
"dashboard",
"workspaces",
"windowtitle"
],
middle: [
"media"
],
right: [
"volume",
"network",
"bluetooth",
"battery",
"systray",
"clock",
"notifications"
]
if (wildcard) {
return layouts[wildcard];
}
if (foundMonitor === undefined) {
return defaultSetup;
}
return curLayouts[foundMonitor];
}
return {
left: ['dashboard', 'workspaces', 'windowtitle'],
middle: ['media'],
right: ['volume', 'network', 'bluetooth', 'battery', 'systray', 'clock', 'notifications'],
};
};
const widget = {
battery: () => WidgetContainer(BatteryLabel()),
dashboard: () => WidgetContainer(Menu()),
workspaces: (monitor: number) => WidgetContainer(Workspaces(monitor)),
windowtitle: () => WidgetContainer(ClientTitle()),
media: () => WidgetContainer(Media()),
notifications: () => WidgetContainer(Notifications()),
volume: () => WidgetContainer(Volume()),
network: () => WidgetContainer(Network()),
bluetooth: () => WidgetContainer(Bluetooth()),
clock: () => WidgetContainer(Clock()),
systray: () => WidgetContainer(SysTray()),
ram: () => WidgetContainer(Ram()),
cpu: () => WidgetContainer(Cpu()),
storage: () => WidgetContainer(Storage()),
netstat: () => WidgetContainer(Netstat()),
kbinput: () => WidgetContainer(KbInput()),
updates: () => WidgetContainer(Updates()),
weather: () => WidgetContainer(Weather()),
power: () => WidgetContainer(Power()),
battery: (): Button<Child, Attribute> => WidgetContainer(BatteryLabel()),
dashboard: (): Button<Child, Attribute> => WidgetContainer(Menu()),
workspaces: (monitor: number): Button<Child, Attribute> => WidgetContainer(Workspaces(monitor)),
windowtitle: (): Button<Child, Attribute> => WidgetContainer(ClientTitle()),
media: (): Button<Child, Attribute> => WidgetContainer(Media()),
notifications: (): Button<Child, Attribute> => WidgetContainer(Notifications()),
volume: (): Button<Child, Attribute> => WidgetContainer(Volume()),
network: (): Button<Child, Attribute> => WidgetContainer(Network()),
bluetooth: (): Button<Child, Attribute> => WidgetContainer(Bluetooth()),
clock: (): Button<Child, Attribute> => WidgetContainer(Clock()),
systray: (): Button<Child, Attribute> => WidgetContainer(SysTray()),
ram: (): Button<Child, Attribute> => WidgetContainer(Ram()),
cpu: (): Button<Child, Attribute> => WidgetContainer(Cpu()),
storage: (): Button<Child, Attribute> => WidgetContainer(Storage()),
netstat: (): Button<Child, Attribute> => WidgetContainer(Netstat()),
kbinput: (): Button<Child, Attribute> => WidgetContainer(KbInput()),
updates: (): Button<Child, Attribute> => WidgetContainer(Updates()),
weather: (): Button<Child, Attribute> => WidgetContainer(Weather()),
power: (): Button<Child, Attribute> => WidgetContainer(Power()),
};
type GdkMonitors = {
[key: string]: {
key: string,
model: string,
used: boolean
}
key: string;
model: string;
used: boolean;
};
};
function getGdkMonitors(): GdkMonitors {
const display = Gdk.Display.get_default();
if (display === null) {
console.error("Failed to get Gdk display.");
console.error('Failed to get Gdk display.');
return {};
}
const numGdkMonitors = display.get_n_monitors();
const gdkMonitors = {};
const gdkMonitors: GdkMonitors = {};
for (let i = 0; i < numGdkMonitors; i++) {
const curMonitor = display.get_monitor(i);
@@ -144,7 +139,7 @@ function getGdkMonitors(): GdkMonitors {
continue;
}
const model = curMonitor.get_model();
const model = curMonitor.get_model() || '';
const geometry = curMonitor.get_geometry();
const scaleFactor = curMonitor.get_scale_factor();
@@ -197,7 +192,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitors = getGdkMonitors();
if (Object.keys(gdkMonitors).length === 0) {
console.error("No GDK monitors were found.");
console.error('No GDK monitors were found.');
return monitor;
}
@@ -205,7 +200,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitor = gdkMonitors[monitor];
// 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}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
});
@@ -216,7 +211,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
}
// 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}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id);
});
@@ -227,7 +222,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
}
// 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) {
usedHyprlandMonitors.add(fallbackMonitor.id);
@@ -250,49 +245,63 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
export const Bar = (() => {
const usedHyprlandMonitors = new Set<number>();
return (monitor: number) => {
return (monitor: number): Window<Child, Attribute> => {
const hyprlandMonitor = gdkMonitorIdToHyprlandId(monitor, usedHyprlandMonitors);
return Widget.Window({
name: `bar-${hyprlandMonitor}`,
class_name: "bar",
class_name: 'bar',
monitor,
visible: true,
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
layer: options.theme.bar.layer.bind("value"),
anchor: location.bind('value').as((ln) => [ln, 'left', 'right']),
exclusivity: 'exclusive',
layer: Utils.merge(
[options.theme.bar.layer.bind('value'), options.tear.bind('value')],
(barLayer: WindowLayer, tear: boolean) => {
if (tear && barLayer === 'overlay') {
return 'top';
}
return barLayer;
},
),
child: Widget.Box({
class_name: 'bar-panel-container',
child: Widget.CenterBox({
class_name: 'bar-panel',
css: 'padding: 1px',
startWidget: Widget.Box({
class_name: "box-left",
class_name: 'box-left',
hexpand: true,
setup: self => {
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = getModulesForMonitor(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>);
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>);
});
},
}),
centerWidget: Widget.Box({
class_name: "box-center",
hpack: "center",
setup: self => {
class_name: 'box-center',
hpack: 'center',
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = getModulesForMonitor(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>);
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>);
});
},
}),
endWidget: Widget.Box({
class_name: "box-right",
hpack: "end",
setup: self => {
class_name: 'box-right',
hpack: 'end',
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = getModulesForMonitor(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>);
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>);
});
},
}),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,32 @@
import Gdk from 'gi://Gdk?version=3.0';
import { openMenu } from "../utils.js";
import options from "options";
import { openMenu } from '../utils.js';
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 {
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 = {
default: "style1",
split: "style2",
wave: "style3",
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `dashboard ${styleMap[style]}`;
}),
child: Widget.Label({
class_name: "bar-menu_label bar-button_icon txt-icon bar",
label: options.bar.launcher.icon.bind("value"),
class_name: 'bar-menu_label bar-button_icon txt-icon bar',
label: options.bar.launcher.icon.bind('value'),
}),
}),
isVisible: true,
boxClass: "dashboard",
boxClass: 'dashboard',
props: {
on_primary_click: (clicked: any, event: Gdk.Event) => {
openMenu(clicked, event, "dashboardmenu");
on_primary_click: (clicked: Button<Child, Child>, event: Gdk.Event): void => {
openMenu(clicked, event, 'dashboardmenu');
},
},
};

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
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
.filter((w) => {
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
* to the center of the button clicked. We don't want the menu to spawn

View File

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

View File

@@ -1,121 +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 { ActiveClient } from 'types/service/hyprland'
import { ActiveClient } from 'types/service/hyprland';
const filterTitle = (windowtitle: ActiveClient) => {
const filterTitle = (windowtitle: ActiveClient): Record<string, string> => {
const windowTitleMap = [
// user provided values
...options.bar.windowtitle.title_map.value,
// Original Entries
["kitty", "󰄛", "Kitty Terminal"],
["firefox", "󰈹", "Firefox"],
["microsoft-edge", "󰇩", "Edge"],
["discord", "", "Discord"],
["vesktop", "", "Vesktop"],
["org.kde.dolphin", "", "Dolphin"],
["plex", "󰚺", "Plex"],
["steam", "", "Steam"],
["spotify", "󰓇", "Spotify"],
["ristretto", "󰋩", "Ristretto"],
["obsidian", "󱓧", "Obsidian"],
['kitty', '󰄛', 'Kitty Terminal'],
['firefox', '󰈹', 'Firefox'],
['microsoft-edge', '󰇩', 'Edge'],
['discord', '', 'Discord'],
['vesktop', '', 'Vesktop'],
['org.kde.dolphin', '', 'Dolphin'],
['plex', '󰚺', 'Plex'],
['steam', '', 'Steam'],
['spotify', '󰓇', 'Spotify'],
['ristretto', '󰋩', 'Ristretto'],
['obsidian', '󱓧', 'Obsidian'],
// Browsers
["google-chrome", "", "Google Chrome"],
["brave-browser", "󰖟", "Brave Browser"],
["chromium", "", "Chromium"],
["opera", "", "Opera"],
["vivaldi", "󰖟", "Vivaldi"],
["waterfox", "󰖟", "Waterfox"],
["thorium", "󰖟", "Waterfox"],
["tor-browser", "", "Tor Browser"],
["floorp", "󰈹", "Floorp"],
['google-chrome', '', 'Google Chrome'],
['brave-browser', '󰖟', 'Brave Browser'],
['chromium', '', 'Chromium'],
['opera', '', 'Opera'],
['vivaldi', '󰖟', 'Vivaldi'],
['waterfox', '󰖟', 'Waterfox'],
['thorium', '󰖟', 'Waterfox'],
['tor-browser', '', 'Tor Browser'],
['floorp', '󰈹', 'Floorp'],
// Terminals
["gnome-terminal", "", "GNOME Terminal"],
["konsole", "", "Konsole"],
["alacritty", "", "Alacritty"],
["wezterm", "", "Wezterm"],
["foot", "󰽒", "Foot Terminal"],
["tilix", "", "Tilix"],
["xterm", "", "XTerm"],
["urxvt", "", "URxvt"],
["st", "", "st Terminal"],
['gnome-terminal', '', 'GNOME Terminal'],
['konsole', '', 'Konsole'],
['alacritty', '', 'Alacritty'],
['wezterm', '', 'Wezterm'],
['foot', '󰽒', 'Foot Terminal'],
['tilix', '', 'Tilix'],
['xterm', '', 'XTerm'],
['urxvt', '', 'URxvt'],
['st', '', 'st Terminal'],
// Development Tools
["code", "󰨞", "Visual Studio Code"],
["vscode", "󰨞", "VS Code"],
["sublime-text", "", "Sublime Text"],
["atom", "", "Atom"],
["android-studio", "󰀴", "Android Studio"],
["intellij-idea", "", "IntelliJ IDEA"],
["pycharm", "󱃖", "PyCharm"],
["webstorm", "󱃖", "WebStorm"],
["phpstorm", "󱃖", "PhpStorm"],
["eclipse", "", "Eclipse"],
["netbeans", "", "NetBeans"],
["docker", "", "Docker"],
["vim", "", "Vim"],
["neovim", "", "Neovim"],
["neovide", "", "Neovide"],
["emacs", "", "Emacs"],
['code', '󰨞', 'Visual Studio Code'],
['vscode', '󰨞', 'VS Code'],
['sublime-text', '', 'Sublime Text'],
['atom', '', 'Atom'],
['android-studio', '󰀴', 'Android Studio'],
['intellij-idea', '', 'IntelliJ IDEA'],
['pycharm', '󱃖', 'PyCharm'],
['webstorm', '󱃖', 'WebStorm'],
['phpstorm', '󱃖', 'PhpStorm'],
['eclipse', '', 'Eclipse'],
['netbeans', '', 'NetBeans'],
['docker', '', 'Docker'],
['vim', '', 'Vim'],
['neovim', '', 'Neovim'],
['neovide', '', 'Neovide'],
['emacs', '', 'Emacs'],
// Communication Tools
["slack", "󰒱", "Slack"],
["telegram-desktop", "", "Telegram"],
["org.telegram.desktop", "", "Telegram"],
["whatsapp", "󰖣", "WhatsApp"],
["teams", "󰊻", "Microsoft Teams"],
["skype", "󰒯", "Skype"],
["thunderbird", "", "Thunderbird"],
['slack', '󰒱', 'Slack'],
['telegram-desktop', '', 'Telegram'],
['org.telegram.desktop', '', 'Telegram'],
['whatsapp', '󰖣', 'WhatsApp'],
['teams', '󰊻', 'Microsoft Teams'],
['skype', '󰒯', 'Skype'],
['thunderbird', '', 'Thunderbird'],
// File Managers
["nautilus", "󰝰", "Files (Nautilus)"],
["thunar", "󰝰", "Thunar"],
["pcmanfm", "󰝰", "PCManFM"],
["nemo", "󰝰", "Nemo"],
["ranger", "󰝰", "Ranger"],
["doublecmd", "󰝰", "Double Commander"],
["krusader", "󰝰", "Krusader"],
['nautilus', '󰝰', 'Files (Nautilus)'],
['thunar', '󰝰', 'Thunar'],
['pcmanfm', '󰝰', 'PCManFM'],
['nemo', '󰝰', 'Nemo'],
['ranger', '󰝰', 'Ranger'],
['doublecmd', '󰝰', 'Double Commander'],
['krusader', '󰝰', 'Krusader'],
// Media Players
["vlc", "󰕼", "VLC Media Player"],
["mpv", "", "MPV"],
["rhythmbox", "󰓃", "Rhythmbox"],
['vlc', '󰕼', 'VLC Media Player'],
['mpv', '', 'MPV'],
['rhythmbox', '󰓃', 'Rhythmbox'],
// Graphics Tools
["gimp", "", "GIMP"],
["inkscape", "", "Inkscape"],
["krita", "", "Krita"],
["blender", "󰂫", "Blender"],
['gimp', '', 'GIMP'],
['inkscape', '', 'Inkscape'],
['krita', '', 'Krita'],
['blender', '󰂫', 'Blender'],
// Video Editing
["kdenlive", "", "Kdenlive"],
['kdenlive', '', 'Kdenlive'],
// Games and Gaming Platforms
["lutris", "󰺵", "Lutris"],
["heroic", "󰺵", "Heroic Games Launcher"],
["minecraft", "󰍳", "Minecraft"],
["csgo", "󰺵", "CS:GO"],
["dota2", "󰺵", "Dota 2"],
['lutris', '󰺵', 'Lutris'],
['heroic', '󰺵', 'Heroic Games Launcher'],
['minecraft', '󰍳', 'Minecraft'],
['csgo', '󰺵', 'CS:GO'],
['dota2', '󰺵', 'Dota 2'],
// Office and Productivity
["evernote", "", "Evernote"],
["sioyek", "", "Sioyek"],
['evernote', '', 'Evernote'],
['sioyek', '', 'Sioyek'],
// Cloud Services and Sync
["dropbox", "󰇣", "Dropbox"],
['dropbox', '󰇣', 'Dropbox'],
// Desktop
["^$", "󰇄", "Desktop"],
['^$', '󰇄', 'Desktop'],
// 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) =>
RegExp(wt[0]).test(windowtitle.class.toLowerCase()),
);
const foundMatch = windowTitleMap.find((wt) => RegExp(wt[0]).test(windowtitle.class.toLowerCase()));
// return the default icon if no match is found or
// if the array element matched is not of size 3
@@ -127,37 +125,93 @@ const filterTitle = (windowtitle: ActiveClient) => {
}
return {
icon: foundMatch ? foundMatch[1] : windowTitleMap[windowTitleMap.length - 1][1],
label: foundMatch ? foundMatch[2] : windowTitleMap[windowTitleMap.length - 1][2]
icon: foundMatch[1],
label: foundMatch[2],
};
};
const ClientTitle = () => {
const getTitle = (client: ActiveClient, useCustomTitle: boolean, useClassName: boolean): string => {
if (useCustomTitle) return filterTitle(client).label;
if (useClassName) return client.class;
const title = client.title;
// If the title is empty or only filled with spaces, fallback to the class name
if (title.length === 0 || title.match(/^ *$/)) {
return client.class;
}
return title;
};
const truncateTitle = (title: string, max_size: number): string => {
if (max_size > 0 && title.length > max_size) {
return title.substring(0, max_size).trim() + '...';
}
return title;
};
const ClientTitle = (): BarBoxChild => {
const { custom_title, class_name, label, icon, truncation, truncation_size } = options.bar.windowtitle;
return {
component: Widget.Box({
className: Utils.merge([options.theme.bar.buttons.style.bind("value"), options.bar.windowtitle.label.bind("value")], (style, showLabel) => {
className: Utils.merge(
[options.theme.bar.buttons.style.bind('value'), label.bind('value')],
(style, showLabel) => {
const styleMap = {
default: "style1",
split: "style2",
wave: "style3",
default: 'style1',
split: 'style2',
wave: 'style3',
wave2: 'style3',
};
return `windowtitle ${styleMap[style]} ${!showLabel ? "no-label" : ""}`;
return `windowtitle ${styleMap[style]} ${!showLabel ? 'no-label' : ''}`;
},
),
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'),
],
(client, useCustomTitle, useClassName, showLabel, showIcon, truncate, truncationSize) => {
if (showIcon) {
return [
Widget.Label({
class_name: 'bar-button-icon windowtitle txt-icon bar',
label: filterTitle(client).icon,
}),
children: options.bar.windowtitle.label.bind("value").as((showLabel) => {
const titleIcon = Widget.Label({
class_name: "bar-button-icon windowtitle txt-icon bar",
label: hyprland.active.bind("client").as((v) => filterTitle(v).icon),
});
const titleLabel = Widget.Label({
class_name: "bar-button-label windowtitle",
label: hyprland.active.bind("client").as((v) => filterTitle(v).label),
});
return showLabel ? [titleIcon, titleLabel] : [titleIcon];
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,
boxClass: "windowtitle",
props: {}
boxClass: 'windowtitle',
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 options from "options";
import { Variable } from "types/variable";
const {
workspaces,
reverse_scroll,
} = options.bar.workspaces;
import { MonitorMap, WorkspaceMap, WorkspaceRule } from 'lib/types/workspace';
import options from 'options';
import { Variable } from 'types/variable';
const { workspaces, reverse_scroll } = options.bar.workspaces;
export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, monitor: number): boolean => {
if (!wsRules || !Object.keys(wsRules).length) {
return true;
}
const monitorMap = {};
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 monitorMap: MonitorMap = {};
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(),
];
monitors.forEach((m) => (monitorMap[m.id] = m.name));
@@ -32,9 +30,9 @@ export const getWorkspacesForMonitor = (curWs: number, wsRules: WorkspaceMap, mo
export const getWorkspaceRules = (): WorkspaceMap => {
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) => {
if (Object.hasOwnProperty.call(workspaceRules, rule.monitor)) {
@@ -60,13 +58,13 @@ export const getCurrentMonitorWorkspaces = (monitor: number): number[] => {
}
const monitorWorkspaces = getWorkspaceRules();
const monitorMap = {};
const monitorMap: MonitorMap = {};
hyprland.monitors.forEach((m) => (monitorMap[m.id] = m.name));
const currentMonitorName = monitorMap[monitor];
return monitorWorkspaces[currentMonitorName];
}
};
export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeWorkspaces: boolean): void => {
if (activeWorkspaces === true) {
@@ -74,18 +72,17 @@ export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
let nextIndex = hyprland.active.workspace.id + 1;
if (nextIndex > activeWses[activeWses.length - 1].id) {
nextIndex = activeWses[0].id;
}
hyprland.messageAsync(`dispatch workspace ${nextIndex}`)
hyprland.messageAsync(`dispatch workspace ${nextIndex}`);
} else if (currentMonitorWorkspaces.value === undefined) {
let nextIndex = hyprland.active.workspace.id + 1;
if (nextIndex > workspaces.value) {
nextIndex = 0;
}
hyprland.messageAsync(`dispatch workspace ${nextIndex}`)
hyprland.messageAsync(`dispatch workspace ${nextIndex}`);
} else {
const curWorkspace = hyprland.active.workspace.id;
const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace);
@@ -94,9 +91,9 @@ export const goToNextWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
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 => {
if (activeWorkspaces === true) {
@@ -104,11 +101,10 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
let prevIndex = hyprland.active.workspace.id - 1;
if (prevIndex < activeWses[0].id) {
prevIndex = activeWses[activeWses.length - 1].id;
}
hyprland.messageAsync(`dispatch workspace ${prevIndex}`)
hyprland.messageAsync(`dispatch workspace ${prevIndex}`);
} else if (currentMonitorWorkspaces.value === undefined) {
let prevIndex = hyprland.active.workspace.id - 1;
@@ -116,7 +112,7 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
prevIndex = workspaces.value;
}
hyprland.messageAsync(`dispatch workspace ${prevIndex}`)
hyprland.messageAsync(`dispatch workspace ${prevIndex}`);
} else {
const curWorkspace = hyprland.active.workspace.id;
const indexOfWs = currentMonitorWorkspaces.value.indexOf(curWorkspace);
@@ -125,11 +121,11 @@ export const goToPrevWS = (currentMonitorWorkspaces: Variable<number[]>, activeW
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;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (!inThrottle) {
@@ -147,7 +143,11 @@ type ThrottledScrollHandlers = {
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(() => {
if (reverse_scroll.value === true) {
goToPrevWS(currentMonitorWorkspaces, activeWorkspaces);
@@ -165,4 +165,4 @@ export const createThrottledScrollHandlers = (scrollSpeed: number, currentMonito
}, 200 / scrollSpeed);
return { throttledScrollUp, throttledScrollDown };
}
};

View File

@@ -1,271 +1,43 @@
const hyprland = await Service.import("hyprland");
import options from "options";
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces, getWorkspaceRules, getWorkspacesForMonitor } from "./helpers";
import { Workspace } from "types/service/hyprland";
import options from 'options';
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces } from './helpers';
import { BarBoxChild, SelfButton } from 'lib/types/bar';
import { occupiedWses } from './variants/occupied';
import { defaultWses } from './variants/default';
const {
workspaces,
monitorSpecific,
workspaceMask,
scroll_speed,
spacing
} = options.bar.workspaces;
const { workspaces, scroll_speed } = options.bar.workspaces;
function range(length: number, start = 1) {
return Array.from({ length }, (_, i) => i + start);
}
const Workspaces = (monitor = -1) => {
const Workspaces = (monitor = -1): BarBoxChild => {
const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor));
workspaces.connect("changed", () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor)
})
const renderClassnames = (showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string, i: number) => {
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";
}
const renderLabel = (showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, i: number, index: number) => {
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}`;
}
const defaultWses = () => {
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, _: number) => {
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,
);
workspaces.connect('changed', () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor);
});
},
})
});
});
},
)
})
}
const occupiedWses = () => {
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),
setup: (self) => {
self.toggleClassName(
"active",
activeId === i,
);
self.toggleClassName(
"occupied",
(hyprland.getWorkspace(i)?.windows || 0) > 0,
);
},
})
});
});
},
)
})
}
return {
component: Widget.Box({
class_name: "workspaces",
child: options.bar.workspaces.hideUnoccupied.bind("value").as(hideUnoccupied => hideUnoccupied ? occupiedWses() : defaultWses()),
class_name: 'workspaces',
child: options.bar.workspaces.hideUnoccupied
.bind('value')
.as((hideUnoccupied) => (hideUnoccupied ? occupiedWses(monitor) : defaultWses(monitor))),
}),
isVisible: true,
boxClass: "workspaces",
boxClass: 'workspaces',
props: {
setup: (self: any) => {
Utils.merge([scroll_speed.bind("value"), options.bar.workspaces.hideUnoccupied.bind("value")], (scroll_speed, hideUnoccupied) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(scroll_speed, currentMonitorWorkspaces, hideUnoccupied)
setup: (self: SelfButton): void => {
Utils.merge(
[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_down = throttledScrollDown;
});
}
}
},
);
},
},
};
};

View File

@@ -0,0 +1,89 @@
import { WorkspaceIconMap } from 'lib/types/workspace';
import { isValidGjsColor } from 'lib/utils';
const hyprland = await Service.import('hyprland');
const getWsIcon = (wsIconMap: WorkspaceIconMap, i: number): string => {
const iconEntry = wsIconMap[i];
if (!iconEntry) {
return `${i}`;
}
const hasIcon = typeof iconEntry === 'object' && 'icon' in iconEntry && iconEntry.icon !== '';
if (typeof iconEntry === 'string' && iconEntry !== '') {
return iconEntry;
}
if (hasIcon) {
return iconEntry.icon;
}
return `${i}`;
};
export const getWsColor = (wsIconMap: WorkspaceIconMap, i: number): string => {
const iconEntry = wsIconMap[i];
if (!iconEntry) {
return '';
}
const hasColor = typeof iconEntry === 'object' && 'color' in iconEntry && iconEntry.color !== '';
if (hasColor && isValidGjsColor(iconEntry.color)) {
return `color: ${iconEntry.color}; border-bottom-color: ${iconEntry.color};`;
}
return '';
};
export const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
showWsIcons: boolean,
i: number,
): string => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered || showWsIcons) {
const numActiveInd = hyprland.active.workspace.id === i ? `${numberedActiveIndicator}` : '';
const className =
`workspace-number can_${numberedActiveIndicator} ` +
`${numActiveInd} ` +
`${showWsIcons ? 'txt-icon' : ''}`;
return className;
}
return 'default';
};
export const renderLabel = (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
showWsIcons: boolean,
wsIconMap: WorkspaceIconMap,
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;
}
}
if (showWsIcons) {
return getWsIcon(wsIconMap, i);
}
return workspaceMask ? `${index + 1}` : `${i}`;
};

View File

@@ -0,0 +1,125 @@
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';
import { getWsColor, renderClassnames, renderLabel } from '../utils';
import { WorkspaceIconMap } from 'lib/types/workspace';
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: Utils.merge(
[
spacing.bind('value'),
options.bar.workspaces.showWsIcons.bind('value'),
options.bar.workspaces.workspaceIconMap.bind('value'),
options.theme.matugen.bind('value'),
],
(
sp: number,
showWsIcons: boolean,
workspaceIconMap: WorkspaceIconMap,
matugen: boolean,
) => {
return (
`margin: 0rem ${0.375 * sp}rem;` +
`${showWsIcons && !matugen ? getWsColor(workspaceIconMap, i) : ''}`
);
},
),
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.showWsIcons.bind('value'),
options.bar.workspaces.icons.available.bind('value'),
options.bar.workspaces.icons.active.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
showWsIcons: boolean,
) => {
return renderClassnames(
showIcons,
showNumbered,
numberedActiveIndicator,
showWsIcons,
i,
);
},
),
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'),
options.bar.workspaces.workspaceIconMap.bind('value'),
options.bar.workspaces.showWsIcons.bind('value'),
workspaceMask.bind('value'),
hyprland.active.workspace.bind('id'),
],
(
showIcons: boolean,
available: string,
active: string,
occupied: string,
wsIconMap: WorkspaceIconMap,
showWsIcons: boolean,
workspaceMask: boolean,
) => {
return renderLabel(
showIcons,
available,
active,
occupied,
workspaceMask,
showWsIcons,
wsIconMap,
i,
index,
monitor,
);
},
),
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,134 @@
const hyprland = await Service.import('hyprland');
import options from 'options';
import { getWorkspaceRules, getWorkspacesForMonitor } from '../helpers';
import { Workspace } from 'types/service/hyprland';
import { getWsColor, renderClassnames, renderLabel } from '../utils';
import { range } from 'lib/utils';
import { BoxWidget } from 'lib/types/widget';
import { WorkspaceIconMap } from 'lib/types/workspace';
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'),
options.bar.workspaces.workspaceIconMap.bind('value'),
options.bar.workspaces.showWsIcons.bind('value'),
options.theme.matugen.bind('value'),
],
(
monitorSpecific: boolean,
wkSpaces: Workspace[],
workspaceMask: boolean,
totalWkspcs: number,
showIcons: boolean,
available: string,
active: string,
occupied: string,
showNumbered: boolean,
numberedActiveIndicator: string,
spacing: number,
activeId: number,
wsIconMap: WorkspaceIconMap,
showWsIcons: boolean,
matugen: boolean,
) => {
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;` +
`${showWsIcons && !matugen ? getWsColor(wsIconMap, i) : ''}`,
class_name: renderClassnames(
showIcons,
showNumbered,
numberedActiveIndicator,
showWsIcons,
i,
),
label: renderLabel(
showIcons,
available,
active,
occupied,
workspaceMask,
showWsIcons,
wsIconMap,
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 = {
"transmission-gtk": "transmission",
"blueberry.py": "blueberry",
Caprine: "facebook-messenger",
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic",
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic",
"audio-headset-bluetooth": "audio-headphones-symbolic",
"audio-card-analog-usb": "audio-speakers-symbolic",
"audio-card-analog-pci": "audio-card-symbolic",
"preferences-system": "emblem-system-symbolic",
"com.github.Aylur.ags-symbolic": "controls-symbolic",
"com.github.Aylur.ags": "controls-symbolic",
'transmission-gtk': 'transmission',
'blueberry.py': 'blueberry',
Caprine: 'facebook-messenger',
'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
'audio-headset-bluetooth': 'audio-headphones-symbolic',
'audio-card-analog-usb': 'audio-speakers-symbolic',
'audio-card-analog-pci': 'audio-card-symbolic',
'preferences-system': 'emblem-system-symbolic',
'com.github.Aylur.ags-symbolic': 'controls-symbolic',
'com.github.Aylur.ags': 'controls-symbolic',
};
export default {
missing: "image-missing-symbolic",
missing: 'image-missing-symbolic',
nix: {
nix: "nix-snowflake-symbolic",
nix: 'nix-snowflake-symbolic',
},
app: {
terminal: "terminal-symbolic",
terminal: 'terminal-symbolic',
},
fallback: {
executable: "application-x-executable",
notification: "dialog-information-symbolic",
video: "video-x-generic-symbolic",
audio: "audio-x-generic-symbolic",
executable: 'application-x-executable',
notification: 'dialog-information-symbolic',
video: 'video-x-generic-symbolic',
audio: 'audio-x-generic-symbolic',
},
ui: {
close: "window-close-symbolic",
colorpicker: "color-select-symbolic",
info: "info-symbolic",
link: "external-link-symbolic",
lock: "system-lock-screen-symbolic",
menu: "open-menu-symbolic",
refresh: "view-refresh-symbolic",
search: "system-search-symbolic",
settings: "emblem-system-symbolic",
themes: "preferences-desktop-theme-symbolic",
tick: "object-select-symbolic",
time: "hourglass-symbolic",
toolbars: "toolbars-symbolic",
warning: "dialog-warning-symbolic",
avatar: "avatar-default-symbolic",
close: 'window-close-symbolic',
colorpicker: 'color-select-symbolic',
info: 'info-symbolic',
link: 'external-link-symbolic',
lock: 'system-lock-screen-symbolic',
menu: 'open-menu-symbolic',
refresh: 'view-refresh-symbolic',
search: 'system-search-symbolic',
settings: 'emblem-system-symbolic',
themes: 'preferences-desktop-theme-symbolic',
tick: 'object-select-symbolic',
time: 'hourglass-symbolic',
toolbars: 'toolbars-symbolic',
warning: 'dialog-warning-symbolic',
avatar: 'avatar-default-symbolic',
arrow: {
right: "pan-end-symbolic",
left: "pan-start-symbolic",
down: "pan-down-symbolic",
up: "pan-up-symbolic",
right: 'pan-end-symbolic',
left: 'pan-start-symbolic',
down: 'pan-down-symbolic',
up: 'pan-up-symbolic',
},
},
audio: {
mic: {
muted: "microphone-disabled-symbolic",
low: "microphone-sensitivity-low-symbolic",
medium: "microphone-sensitivity-medium-symbolic",
high: "microphone-sensitivity-high-symbolic",
muted: 'microphone-disabled-symbolic',
low: 'microphone-sensitivity-low-symbolic',
medium: 'microphone-sensitivity-medium-symbolic',
high: 'microphone-sensitivity-high-symbolic',
},
volume: {
muted: "audio-volume-muted-symbolic",
low: "audio-volume-low-symbolic",
medium: "audio-volume-medium-symbolic",
high: "audio-volume-high-symbolic",
overamplified: "audio-volume-overamplified-symbolic",
muted: 'audio-volume-muted-symbolic',
low: 'audio-volume-low-symbolic',
medium: 'audio-volume-medium-symbolic',
high: 'audio-volume-high-symbolic',
overamplified: 'audio-volume-overamplified-symbolic',
},
type: {
headset: "audio-headphones-symbolic",
speaker: "audio-speakers-symbolic",
card: "audio-card-symbolic",
headset: 'audio-headphones-symbolic',
speaker: 'audio-speakers-symbolic',
card: 'audio-card-symbolic',
},
mixer: "mixer-symbolic",
mixer: 'mixer-symbolic',
},
powerprofile: {
balanced: "power-profile-balanced-symbolic",
"power-saver": "power-profile-power-saver-symbolic",
performance: "power-profile-performance-symbolic",
balanced: 'power-profile-balanced-symbolic',
'power-saver': 'power-profile-power-saver-symbolic',
performance: 'power-profile-performance-symbolic',
},
asusctl: {
profile: {
Balanced: "power-profile-balanced-symbolic",
Quiet: "power-profile-power-saver-symbolic",
Performance: "power-profile-performance-symbolic",
Balanced: 'power-profile-balanced-symbolic',
Quiet: 'power-profile-power-saver-symbolic',
Performance: 'power-profile-performance-symbolic',
},
mode: {
Integrated: "processor-symbolic",
Hybrid: "controller-symbolic",
Integrated: 'processor-symbolic',
Hybrid: 'controller-symbolic',
},
},
battery: {
charging: "battery-flash-symbolic",
warning: "battery-empty-symbolic",
charging: 'battery-flash-symbolic',
warning: 'battery-empty-symbolic',
},
bluetooth: {
enabled: "bluetooth-active-symbolic",
disabled: "bluetooth-disabled-symbolic",
enabled: 'bluetooth-active-symbolic',
disabled: 'bluetooth-disabled-symbolic',
},
brightness: {
indicator: "display-brightness-symbolic",
keyboard: "keyboard-brightness-symbolic",
screen: "display-brightness-symbolic",
indicator: 'display-brightness-symbolic',
keyboard: 'keyboard-brightness-symbolic',
screen: 'display-brightness-symbolic',
},
powermenu: {
sleep: "weather-clear-night-symbolic",
reboot: "system-reboot-symbolic",
logout: "system-log-out-symbolic",
shutdown: "system-shutdown-symbolic",
sleep: 'weather-clear-night-symbolic',
reboot: 'system-reboot-symbolic',
logout: 'system-log-out-symbolic',
shutdown: 'system-shutdown-symbolic',
},
recorder: {
recording: "media-record-symbolic",
recording: 'media-record-symbolic',
},
notifications: {
noisy: "org.gnome.Settings-notifications-symbolic",
silent: "notifications-disabled-symbolic",
message: "chat-bubbles-symbolic",
noisy: 'org.gnome.Settings-notifications-symbolic',
silent: 'notifications-disabled-symbolic',
message: 'chat-bubbles-symbolic',
},
trash: {
full: "user-trash-full-symbolic",
empty: "user-trash-symbolic",
full: 'user-trash-full-symbolic',
empty: 'user-trash-symbolic',
},
mpris: {
shuffle: {
enabled: "media-playlist-shuffle-symbolic",
disabled: "media-playlist-consecutive-symbolic",
enabled: 'media-playlist-shuffle-symbolic',
disabled: 'media-playlist-consecutive-symbolic',
},
loop: {
none: "media-playlist-repeat-symbolic",
track: "media-playlist-repeat-song-symbolic",
playlist: "media-playlist-repeat-symbolic",
none: 'media-playlist-repeat-symbolic',
track: 'media-playlist-repeat-song-symbolic',
playlist: 'media-playlist-repeat-symbolic',
},
playing: "media-playback-pause-symbolic",
paused: "media-playback-start-symbolic",
stopped: "media-playback-start-symbolic",
prev: "media-skip-backward-symbolic",
next: "media-skip-forward-symbolic",
playing: 'media-playback-pause-symbolic',
paused: 'media-playback-start-symbolic',
stopped: 'media-playback-start-symbolic',
prev: 'media-skip-backward-symbolic',
next: 'media-skip-forward-symbolic',
},
system: {
cpu: "org.gnome.SystemMonitor-symbolic",
ram: "drive-harddisk-solidstate-symbolic",
temp: "temperature-symbolic",
cpu: 'org.gnome.SystemMonitor-symbolic',
ram: 'drive-harddisk-solidstate-symbolic',
temp: 'temperature-symbolic',
},
color: {
dark: "dark-mode-symbolic",
light: "light-mode-symbolic",
dark: 'dark-mode-symbolic',
light: 'light-mode-symbolic',
},
weather: {
warning: "dialog-warning-symbolic",
sunny: "weather-clear-symbolic",
clear: "weather-clear-night-symbolic",
partly_cloudy: "weather-few-clouds-symbolic",
partly_cloudy_night: "weather-few-clouds-night-symbolic",
cloudy: "weather-overcast-symbolic",
overcast: "weather-overcast-symbolic",
mist: "weather-overcast-symbolic",
patchy_rain_nearby: "weather-showers-scattered-symbolic",
patchy_rain_possible: "weather-showers-scattered-symbolic",
patchy_snow_possible: "weather-snow-symbolic",
patchy_sleet_possible: "weather-snow-symbolic",
patchy_freezing_drizzle_possible: "weather-showers-scattered-symbolic",
thundery_outbreaks_possible: "weather-overcast-symbolic",
blowing_snow: "weather-snow-symbolic",
blizzard: "weather-snow-symbolic",
fog: "weather-fog-symbolic",
freezing_fog: "weather-fog-symbolic",
patchy_light_drizzle: "weather-showers-scattered-symbolic",
light_drizzle: "weather-showers-symbolic",
freezing_drizzle: "weather-showers-symbolic",
heavy_freezing_drizzle: "weather-showers-symbolic",
patchy_light_rain: "weather-showers-scattered-symbolic",
light_rain: "weather-showers-symbolic",
moderate_rain_at_times: "weather-showers-symbolic",
moderate_rain: "weather-showers-symbolic",
heavy_rain_at_times: "weather-showers-symbolic",
heavy_rain: "weather-showers-symbolic",
light_freezing_rain: "weather-showers-symbolic",
moderate_or_heavy_freezing_rain: "weather-showers-symbolic",
light_sleet: "weather-snow-symbolic",
moderate_or_heavy_sleet: "weather-snow-symbolic",
patchy_light_snow: "weather-snow-symbolic",
light_snow: "weather-snow-symbolic",
patchy_moderate_snow: "weather-snow-symbolic",
moderate_snow: "weather-snow-symbolic",
patchy_heavy_snow: "weather-snow-symbolic",
heavy_snow: "weather-snow-symbolic",
ice_pellets: "weather-showers-symbolic",
light_rain_shower: "weather-showers-symbolic",
moderate_or_heavy_rain_shower: "weather-showers-symbolic",
torrential_rain_shower: "weather-showers-symbolic",
light_sleet_showers: "weather-showers-symbolic",
moderate_or_heavy_sleet_showers: "weather-showers-symbolic",
light_snow_showers: "weather-snow-symbolic",
moderate_or_heavy_snow_showers: "weather-snow-symbolic",
light_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",
moderate_or_heavy_rain_with_thunder: "weather-showers-symbolic",
patchy_light_snow_with_thunder: "weather-snow-symbolic",
moderate_or_heavy_snow_with_thunder: "weather-snow-symbolic",
warning: 'dialog-warning-symbolic',
sunny: 'weather-clear-symbolic',
clear: 'weather-clear-night-symbolic',
partly_cloudy: 'weather-few-clouds-symbolic',
partly_cloudy_night: 'weather-few-clouds-night-symbolic',
cloudy: 'weather-overcast-symbolic',
overcast: 'weather-overcast-symbolic',
mist: 'weather-overcast-symbolic',
patchy_rain_nearby: 'weather-showers-scattered-symbolic',
patchy_rain_possible: 'weather-showers-scattered-symbolic',
patchy_snow_possible: 'weather-snow-symbolic',
patchy_sleet_possible: 'weather-snow-symbolic',
patchy_freezing_drizzle_possible: 'weather-showers-scattered-symbolic',
thundery_outbreaks_possible: 'weather-overcast-symbolic',
blowing_snow: 'weather-snow-symbolic',
blizzard: 'weather-snow-symbolic',
fog: 'weather-fog-symbolic',
freezing_fog: 'weather-fog-symbolic',
patchy_light_drizzle: 'weather-showers-scattered-symbolic',
light_drizzle: 'weather-showers-symbolic',
freezing_drizzle: 'weather-showers-symbolic',
heavy_freezing_drizzle: 'weather-showers-symbolic',
patchy_light_rain: 'weather-showers-scattered-symbolic',
light_rain: 'weather-showers-symbolic',
moderate_rain_at_times: 'weather-showers-symbolic',
moderate_rain: 'weather-showers-symbolic',
heavy_rain_at_times: 'weather-showers-symbolic',
heavy_rain: 'weather-showers-symbolic',
light_freezing_rain: 'weather-showers-symbolic',
moderate_or_heavy_freezing_rain: 'weather-showers-symbolic',
light_sleet: 'weather-snow-symbolic',
moderate_or_heavy_sleet: 'weather-snow-symbolic',
patchy_light_snow: 'weather-snow-symbolic',
light_snow: 'weather-snow-symbolic',
patchy_moderate_snow: 'weather-snow-symbolic',
moderate_snow: 'weather-snow-symbolic',
patchy_heavy_snow: 'weather-snow-symbolic',
heavy_snow: 'weather-snow-symbolic',
ice_pellets: 'weather-showers-symbolic',
light_rain_shower: 'weather-showers-symbolic',
moderate_or_heavy_rain_shower: 'weather-showers-symbolic',
torrential_rain_shower: 'weather-showers-symbolic',
light_sleet_showers: 'weather-showers-symbolic',
moderate_or_heavy_sleet_showers: 'weather-showers-symbolic',
light_snow_showers: 'weather-snow-symbolic',
moderate_or_heavy_snow_showers: 'weather-snow-symbolic',
light_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',
moderate_or_heavy_rain_with_thunder: 'weather-showers-symbolic',
patchy_light_snow_with_thunder: 'weather-snow-symbolic',
moderate_or_heavy_snow_with_thunder: 'weather-snow-symbolic',
},
};
} as const;

54
modules/icons/weather.ts Normal file
View File

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

View File

@@ -1,184 +0,0 @@
const hyprland = await Service.import("hyprland");
import { Exclusivity } from "lib/types/widget";
import { bash } from "lib/utils";
import { Monitor } from "types/service/hyprland";
export const Padding = (name: string) =>
Widget.EventBox({
hexpand: true,
vexpand: true,
can_focus: true,
child: Widget.Box(),
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
});
const moveBoxToCursor = (self: any, fixed: boolean) => {
if (fixed) {
return;
}
globalMousePos.connect("changed", async ({ value }) => {
const curHyprlandMonitor = hyprland.monitors.find(m => m.id === hyprland.active.monitor.id);
const dropdownWidth = self.child.get_allocation().width;
let hyprScaling = 1;
try {
const monitorInfo = await bash('hyprctl monitors -j');
const parsedMonitorInfo = JSON.parse(monitorInfo);
const foundMonitor = parsedMonitorInfo.find((monitor: Monitor) =>
monitor.id === hyprland.active.monitor.id
);
hyprScaling = foundMonitor?.scale || 1;
} catch (error) {
console.error(`Error parsing hyprland monitors: ${error}`);
}
let monWidth = curHyprlandMonitor?.width;
let monHeight = curHyprlandMonitor?.height;
if (monWidth === undefined || monHeight === undefined || hyprScaling === undefined) {
return;
}
// If GDK Scaling is applied, then get divide width by scaling
// to get the proper coordinates.
// Ex: On a 2860px wide monitor... if scaling is set to 2, then the right
// end of the monitor is the 1430th pixel.
const gdkScale = Utils.exec('bash -c "echo $GDK_SCALE"');
if (/^\d+(.\d+)?$/.test(gdkScale)) {
const scale = parseFloat(gdkScale);
monWidth = monWidth / scale;
monHeight = monHeight / scale;
} else {
monWidth = monWidth / hyprScaling;
monHeight = monHeight / hyprScaling;
}
// If monitor is vertical (transform = 1 || 3) swap height and width
const isVertical = curHyprlandMonitor?.transform !== undefined
? curHyprlandMonitor.transform % 2 !== 0
: false;
if (isVertical) {
[monWidth, monHeight] = [monHeight, monWidth];
}
let marginRight = monWidth - dropdownWidth / 2;
marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0];
let marginLeft = monWidth - dropdownWidth - marginRight;
const minimumMargin = 0;
if (marginRight < minimumMargin) {
marginRight = minimumMargin;
marginLeft = monWidth - dropdownWidth - minimumMargin;
}
if (marginLeft < minimumMargin) {
marginLeft = minimumMargin;
marginRight = monWidth - dropdownWidth - minimumMargin;
}
const marginTop = 45;
const marginBottom = monHeight - marginTop;
self.set_margin_left(marginLeft);
self.set_margin_right(marginRight);
self.set_margin_bottom(marginBottom);
});
};
// NOTE: We make the window visible for 2 seconds (on startup) so the child
// elements can allocat their proper dimensions.
// Otherwise the width that we rely on for menu positioning is set improperly
// for the first time we open a menu of each type.
const initRender = Variable(true);
setTimeout(() => {
initRender.value = false;
}, 2000);
export default ({
name,
child,
layout = "center",
transition,
exclusivity = "ignore" as Exclusivity,
fixed = false,
...props
}) =>
Widget.Window({
name,
class_names: [name, "dropdown-menu"],
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
visible: initRender.bind("value"),
keymode: "on-demand",
exclusivity,
layer: "top",
anchor: ["top", "left"],
child: Widget.EventBox({
class_name: "parent-event",
on_primary_click: () => App.closeWindow(name),
on_secondary_click: () => App.closeWindow(name),
child: Widget.Box({
class_name: "top-eb",
vertical: true,
children: [
Widget.EventBox({
class_name: "mid-eb event-top-padding-static",
hexpand: true,
vexpand: false,
can_focus: false,
child: Widget.Box(),
setup: (w) => {
w.on("button-press-event", () => App.toggleWindow(name));
w.set_margin_top(1);
},
}),
Widget.EventBox({
class_name: "mid-eb event-top-padding",
hexpand: true,
vexpand: false,
can_focus: false,
child: Widget.Box(),
setup: (w) => {
w.on("button-press-event", () => App.toggleWindow(name));
w.set_margin_top(1);
},
}),
Widget.EventBox({
class_name: "in-eb menu-event-box",
on_primary_click: () => {
return true;
},
on_secondary_click: () => {
return true;
},
setup: (self) => {
moveBoxToCursor(self, fixed);
},
child: Widget.Box({
class_name: "dropdown-menu-container",
css: "padding: 1px; margin: -1px;",
child: Widget.Revealer({
revealChild: false,
setup: (self) =>
self.hook(App, (_, wname, visible) => {
if (wname === name) self.reveal_child = visible;
}),
transition: "crossfade",
transitionDuration: 350,
child: Widget.Box({
class_name: "dropdown-menu-container",
can_focus: true,
children: [child],
}),
}),
}),
}),
],
}),
}),
...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';
const renderActiveInput = () => {
const renderActiveInput = (): BarBoxChild => {
return [
Widget.Box({
class_name: "menu-slider-container input",
class_name: 'menu-slider-container input',
children: [
Widget.Button({
vexpand: false,
vpack: "end",
vpack: 'end',
setup: (self) => {
self.hook(audio, () => {
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);
});
},
on_primary_click: () =>
(audio.microphone.is_muted = !audio.microphone.is_muted),
on_primary_click: () => (audio.microphone.is_muted = !audio.microphone.is_muted),
child: Widget.Icon({
class_name: "menu-active-icon input",
class_name: 'menu-active-icon input',
setup: (self) => {
self.hook(audio, () => {
self.icon = getIcon(
audio.microphone.volume,
audio.microphone.is_muted,
)["mic"];
const isMicMuted =
audio.microphone.is_muted !== null ? audio.microphone.is_muted : true;
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,
children: [
Widget.Label({
class_name: "menu-active input",
hpack: "start",
truncate: "end",
class_name: 'menu-active input',
hpack: 'start',
truncate: 'end',
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({
value: audio.microphone.bind("volume").as((v) => v),
class_name: "menu-active-slider menu-slider inputs",
value: audio.microphone.bind('volume').as((v) => v),
class_name: 'menu-active-slider menu-slider inputs',
draw_value: false,
hexpand: true,
min: 0,
@@ -52,11 +59,9 @@ const renderActiveInput = () => {
],
}),
Widget.Label({
class_name: "menu-active-percentage input",
vpack: "end",
label: audio.microphone
.bind("volume")
.as((v) => `${Math.round(v * 100)}%`),
class_name: 'menu-active-percentage input',
vpack: 'end',
label: audio.microphone.bind('volume').as((v) => `${Math.round(v * 100)}%`),
}),
],
}),

View File

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

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