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 .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/> <br/>
# HyprPanel 🚀 # HyprPanel 🚀
A panel built for Hyprland with [AGS](https://github.com/Aylur/ags) A panel built for Hyprland with [AGS](https://github.com/Aylur/ags)
![HyprPanel](./assets/HyprPanel.png) ![HyprPanel](./assets/HyprPanel.png)
## Installation ## 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. 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 ## Requirements
Bun Bun
```sh ```sh
curl -fsSL https://bun.sh/install | bash && \ curl -fsSL https://bun.sh/install | bash && \
sudo ln -s $HOME/.bun/bin/bun /usr/local/bin/bun sudo ln -s $HOME/.bun/bin/bun /usr/local/bin/bun
``` ```
Additional dependencies: Additional dependencies:
```sh ```sh
pipewire pipewire
libgtop libgtop
@@ -45,53 +50,110 @@ gnome-bluetooth-3.0
``` ```
Optional Dependencies: Optional Dependencies:
```sh ```sh
## Used for Tracking GPU Usage in your Dashboard (NVidia only) ## Used for Tracking GPU Usage in your Dashboard (NVidia only)
python python
python-gpustat 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 ```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 ```bash
yay -S grimblast-git gpu-screen-recorder hyprpicker matugen-bin python-gpustat aylurs-gtk-shell-git 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). For NixOS/Home-Manager, see [NixOS & Home-Manager instructions](#nixos--home-manager).
## Instructions ## Instructions
### AGS ### AGS
Once everything is installed you need to put the contents of this repo in `~/.config/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: If you already have something in `~/.config/ags`, it's recommended that you back it up with:
```bash ```bash
mv $HOME/.config/ags $HOME/.config/ags.bkup mv $HOME/.config/ags $HOME/.config/ags.bkup
``` ```
Otherwise you can use this command to install the panel: Otherwise you can use this command to install the panel:
```bash ```bash
git clone https://github.com/Jas-SinghFSU/HyprPanel.git && \ git clone https://github.com/Jas-SinghFSU/HyprPanel.git && \
ln -s $(pwd)/HyprPanel $HOME/.config/ags ln -s $(pwd)/HyprPanel $HOME/.config/ags
``` ```
### Nerd Fonts ### 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. 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 ### Launch the panel
Afterwards you can run the panel with the following command in your terminal: Afterwards you can run the panel with the following command in your terminal:
```bash ```bash
ags ags
``` ```
Or you can add it to your Hyprland config (hyprland.conf) to auto-start with: Or you can add it to your Hyprland config (hyprland.conf) to auto-start with:
```bash ```bash
exec-once = ags exec-once = ags
``` ```
### NixOS & Home-Manager ### 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. 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 ```nix
# flake.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 ### Specifying bar layouts per monitor
To specify layouts for each monitor you can create a JSON object such as: To specify layouts for each monitor you can create a JSON object such as:
```JSON ```JSON
{ {
"0": { "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: 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 ```js
"battery" 'battery';
"dashboard" 'dashboard';
"workspaces" 'workspaces';
"windowtitle" 'windowtitle';
"media" 'media';
"notifications" 'notifications';
"volume" 'volume';
"network" 'network';
"bluetooth" 'bluetooth';
"clock" 'clock';
"systray" '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". 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 ### 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 main = '/tmp/ags/hyprpanel/main.js';
const entry = `${App.configDir}/main.ts` const entry = `${App.configDir}/main.ts`;
const bundler = GLib.getenv("AGS_BUNDLER") || "bun" const bundler = GLib.getenv('AGS_BUNDLER') || 'bun';
const v = { const v = {
ags: pkg.version?.split(".").map(Number) || [], ags: pkg.version?.split('.').map(Number) || [],
expect: [1, 8, 1], expect: [1, 8, 1],
} };
try { try {
switch (bundler) { switch (bundler) {
case "bun": await Utils.execAsync([ case 'bun':
"bun", "build", entry, await Utils.execAsync([
"--outfile", main, 'bun',
"--external", "resource://*", 'build',
"--external", "gi://*", entry,
"--external", "file://*", '--outfile',
]); break main,
'--external',
'resource://*',
'--external',
'gi://*',
'--external',
'file://*',
]);
break;
case "esbuild": await Utils.execAsync([ case 'esbuild':
"esbuild", "--bundle", entry, await Utils.execAsync([
"--format=esm", 'esbuild',
`--outfile=${main}`, '--bundle',
"--external:resource://*", entry,
"--external:gi://*", '--format=esm',
"--external:file://*", `--outfile=${main}`,
]); break '--external:resource://*',
'--external:gi://*',
'--external:file://*',
]);
break;
default: default:
throw `"${bundler}" is not a valid bundler` throw `"${bundler}" is not a valid bundler`;
} }
if (v.ags[1] < v.expect[1] || v.ags[2] < v.expect[2]) { if (v.ags[1] < v.expect[1] || v.ags[2] < v.expect[2]) {
print(`HyprPanel needs atleast v${v.expect.join(".")} of AGS, yours is v${v.ags.join(".")}`) print(`HyprPanel needs atleast v${v.expect.join('.')} of AGS, yours is v${v.ags.join('.')}`);
App.quit() App.quit();
} }
await import(`file://${main}`) await import(`file://${main}`);
} catch (error) { } catch (error) {
console.error(error) console.error(error);
App.quit() App.quit();
} }
export { } export {};

View File

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

View File

@@ -2,8 +2,10 @@ import { Option } from 'widget/settings/shared/Option';
import { Header } from 'widget/settings/shared/Header'; import { Header } from 'widget/settings/shared/Header';
import options from 'options'; import options from 'options';
import Scrollable from 'types/widgets/scrollable';
import { Attribute, GtkWidget } from 'lib/types/widget';
export const CustomModuleSettings = () => export const CustomModuleSettings = (): Scrollable<GtkWidget, Attribute> =>
Widget.Scrollable({ Widget.Scrollable({
vscroll: 'automatic', vscroll: 'automatic',
hscroll: 'automatic', hscroll: 'automatic',
@@ -13,10 +15,10 @@ export const CustomModuleSettings = () =>
vertical: true, vertical: true,
children: [ children: [
/* /*
************************************ ************************************
* GENERAL * * GENERAL *
************************************ ************************************
*/ */
Header('General'), Header('General'),
Option({ Option({
opt: options.bar.customModules.scrollSpeed, opt: options.bar.customModules.scrollSpeed,
@@ -25,10 +27,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* RAM * * RAM *
************************************ ************************************
*/ */
Header('RAM'), Header('RAM'),
Option({ Option({
opt: options.bar.customModules.ram.label, opt: options.bar.customModules.ram.label,
@@ -76,10 +78,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* CPU * * CPU *
************************************ ************************************
*/ */
Header('CPU'), Header('CPU'),
Option({ Option({
opt: options.bar.customModules.cpu.label, opt: options.bar.customModules.cpu.label,
@@ -131,10 +133,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* STORAGE * * STORAGE *
************************************ ************************************
*/ */
Header('Storage'), Header('Storage'),
Option({ Option({
opt: options.bar.customModules.storage.icon, opt: options.bar.customModules.storage.icon,
@@ -188,10 +190,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* NETSTAT * * NETSTAT *
************************************ ************************************
*/ */
Header('Netstat'), Header('Netstat'),
Option({ Option({
opt: options.bar.customModules.netstat.networkInterface, opt: options.bar.customModules.netstat.networkInterface,
@@ -204,17 +206,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.netstat.icon, opt: options.bar.customModules.netstat.icon,
title: 'Netstat Icon', title: 'Netstat Icon',
type: 'enum', type: 'enum',
enums: [ enums: ['󰖟', '󰇚', '󰕒', '󰛳', '', '󰣺', '󰖩', '', '󰈀'],
'󰖟',
'󰇚',
'󰕒',
'󰛳',
'',
'󰣺',
'󰖩',
'',
'󰈀',
],
}), }),
Option({ Option({
opt: options.bar.customModules.netstat.label, opt: options.bar.customModules.netstat.label,
@@ -268,10 +260,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* KEYBOARD LAYOUT * * KEYBOARD LAYOUT *
************************************ ************************************
*/ */
Header('Keyboard Layout'), Header('Keyboard Layout'),
Option({ Option({
opt: options.bar.customModules.kbLayout.icon, opt: options.bar.customModules.kbLayout.icon,
@@ -322,10 +314,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* UPDATES * * UPDATES *
************************************ ************************************
*/ */
Header('Updates'), Header('Updates'),
Option({ Option({
opt: options.bar.customModules.updates.updateCommand, opt: options.bar.customModules.updates.updateCommand,
@@ -336,17 +328,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.updates.icon, opt: options.bar.customModules.updates.icon,
title: 'Updates Icon', title: 'Updates Icon',
type: 'enum', type: 'enum',
enums: [ enums: ['󰚰', '󰇚', '', '󱑢', '󱑣', '󰏖', '', '󰏔', '󰏗'],
'󰚰',
'󰇚',
'',
'󱑢',
'󱑣',
'󰏖',
'',
'󰏔',
'󰏗',
],
}), }),
Option({ Option({
opt: options.bar.customModules.updates.label, opt: options.bar.customModules.updates.label,
@@ -367,7 +349,7 @@ export const CustomModuleSettings = () =>
opt: options.bar.customModules.updates.pollingInterval, opt: options.bar.customModules.updates.pollingInterval,
title: 'Polling Interval', title: 'Polling Interval',
type: 'number', type: 'number',
subtitle: "WARNING: Be careful of your package manager\'s rate limit.", subtitle: "WARNING: Be careful of your package manager's rate limit.",
min: 100, min: 100,
max: 60 * 24 * 1000, max: 60 * 24 * 1000,
increment: 1000, increment: 1000,
@@ -399,10 +381,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* WEATHER * * WEATHER *
************************************ ************************************
*/ */
Header('Weather'), Header('Weather'),
Option({ Option({
opt: options.bar.customModules.weather.label, opt: options.bar.customModules.weather.label,
@@ -447,10 +429,10 @@ export const CustomModuleSettings = () =>
}), }),
/* /*
************************************ ************************************
* POWER * * POWER *
************************************ ************************************
*/ */
Header('Power'), Header('Power'),
Option({ Option({
opt: options.theme.bar.buttons.modules.power.spacing, opt: options.theme.bar.buttons.modules.power.spacing,
@@ -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'; import GTop from 'gi://GTop';
const defaultCpuData: number = 0;
let previousCpuData = new GTop.glibtop_cpu(); let previousCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(previousCpuData); GTop.glibtop_get_cpu(previousCpuData);
export const computeCPU = () => { export const computeCPU = (): number => {
const currentCpuData = new GTop.glibtop_cpu(); const currentCpuData = new GTop.glibtop_cpu();
GTop.glibtop_get_cpu(currentCpuData); GTop.glibtop_get_cpu(currentCpuData);
@@ -19,5 +17,4 @@ export const computeCPU = () => {
previousCpuData = currentCpuData; previousCpuData = currentCpuData;
return cpuUsagePercentage; return cpuUsagePercentage;
} };

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,41 +1,30 @@
import options from "options"; import options from 'options';
import { module } from "../module" import { module } from '../module';
import { inputHandler } from "customModules/utils"; import { inputHandler } from 'customModules/utils';
import Gtk from "types/@girs/gtk-3.0/gtk-3.0"; import Gtk from 'types/@girs/gtk-3.0/gtk-3.0';
import Button from "types/widgets/button"; import Button from 'types/widgets/button';
import { getWeatherStatusIcon, globalWeatherVar } from "globals/weather"; import { getWeatherStatusTextIcon, globalWeatherVar } from 'globals/weather';
import { Module } from 'lib/types/bar';
const { const { label, unit, leftClick, rightClick, middleClick, scrollUp, scrollDown } = options.bar.customModules.weather;
label,
unit,
leftClick,
rightClick,
middleClick,
scrollUp,
scrollDown,
} = options.bar.customModules.weather;
export const Weather = () => { export const Weather = (): Module => {
const weatherModule = module({
const networkModule = module({ textIcon: Utils.merge([globalWeatherVar.bind('value')], (wthr) => {
icon: Utils.merge([globalWeatherVar.bind("value")], (wthr) => { const weatherStatusIcon = getWeatherStatusTextIcon(wthr);
const weatherStatusIcon = getWeatherStatusIcon(wthr);
return weatherStatusIcon; return weatherStatusIcon;
}), }),
tooltipText: globalWeatherVar.bind("value").as(v => `Weather Status: ${v.current.condition.text}`), tooltipText: globalWeatherVar.bind('value').as((v) => `Weather Status: ${v.current.condition.text}`),
boxClass: "weather-custom", boxClass: 'weather-custom',
label: Utils.merge( label: Utils.merge([globalWeatherVar.bind('value'), unit.bind('value')], (wthr, unt) => {
[globalWeatherVar.bind("value"), unit.bind("value")], if (unt === 'imperial') {
(wthr, unt) => { return `${Math.ceil(wthr.current.temp_f)}° F`;
if (unt === "imperial") { } else {
return `${Math.ceil(wthr.current.temp_f)}° F`; return `${Math.ceil(wthr.current.temp_c)}° C`;
} else { }
return `${Math.ceil(wthr.current.temp_c)}° C`; }),
} showLabelBinding: label.bind('value'),
},
),
showLabelBinding: label.bind("value"),
props: { props: {
setup: (self: Button<Gtk.Widget, Gtk.Widget>) => { setup: (self: Button<Gtk.Widget, Gtk.Widget>) => {
inputHandler(self, { inputHandler(self, {
@@ -59,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 Service from 'resource:///com/github/Aylur/ags/service.js';
import App from "resource:///com/github/Aylur/ags/app.js"; import App from 'resource:///com/github/Aylur/ags/app.js';
import { monitorFile } from "resource:///com/github/Aylur/ags/utils.js"; import { monitorFile } from 'resource:///com/github/Aylur/ags/utils.js';
import Gio from "gi://Gio"; import Gio from 'gi://Gio';
import { FileInfo } from "types/@girs/gio-2.0/gio-2.0.cjs"; import { FileInfo } from 'types/@girs/gio-2.0/gio-2.0.cjs';
class DirectoryMonitorService extends Service { class DirectoryMonitorService extends Service {
static { static {
@@ -14,23 +14,19 @@ class DirectoryMonitorService extends Service {
this.recursiveDirectoryMonitor(`${App.configDir}/scss`); this.recursiveDirectoryMonitor(`${App.configDir}/scss`);
} }
recursiveDirectoryMonitor(directoryPath: string) { recursiveDirectoryMonitor(directoryPath: string): void {
monitorFile(directoryPath, (_, eventType) => { monitorFile(directoryPath, (_, eventType) => {
if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) { if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
this.emit("changed"); this.emit('changed');
} }
}); });
const directory = Gio.File.new_for_path(directoryPath); const directory = Gio.File.new_for_path(directoryPath);
const enumerator = directory.enumerate_children( const enumerator = directory.enumerate_children('standard::*', Gio.FileQueryInfoFlags.NONE, null);
"standard::*",
Gio.FileQueryInfoFlags.NONE,
null,
);
let fileInfo: FileInfo; let fileInfo: FileInfo;
while ((fileInfo = enumerator.next_file(null) as FileInfo) !== null) { while ((fileInfo = enumerator.next_file(null) as FileInfo) !== null) {
const childPath = directoryPath + "/" + fileInfo.get_name(); const childPath = directoryPath + '/' + fileInfo.get_name();
if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) { if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) {
this.recursiveDirectoryMonitor(childPath); this.recursiveDirectoryMonitor(childPath);
} }

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

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 options from 'options';
import { UnitType, Weather } from "lib/types/weather.js"; import { UnitType, Weather, WeatherIconTitle, WeatherIcon } from 'lib/types/weather.js';
import { DEFAULT_WEATHER } from "lib/types/defaults/weather.js"; import { DEFAULT_WEATHER } from 'lib/types/defaults/weather.js';
import GLib from "gi://GLib?version=2.0" import GLib from 'gi://GLib?version=2.0';
import { weatherIcons } from 'modules/icons/weather.js';
import icons from "../modules/icons/index.js";
const { key, interval, location } = options.menus.clock.weather; const { key, interval, location } = options.menus.clock.weather;
@@ -11,12 +10,12 @@ export const globalWeatherVar = Variable<Weather>(DEFAULT_WEATHER);
let weatherIntervalInstance: null | number = null; let weatherIntervalInstance: null | number = null;
const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: string) => { const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: string): void => {
if (weatherIntervalInstance !== null) { if (weatherIntervalInstance !== null) {
GLib.source_remove(weatherIntervalInstance); GLib.source_remove(weatherIntervalInstance);
} }
const formattedLocation = loc.replace(" ", "%20"); const formattedLocation = loc.replace(' ', '%20');
weatherIntervalInstance = Utils.interval(weatherInterval, () => { weatherIntervalInstance = Utils.interval(weatherInterval, () => {
Utils.execAsync( Utils.execAsync(
@@ -24,17 +23,17 @@ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: str
) )
.then((res) => { .then((res) => {
try { try {
if (typeof res !== "string") { if (typeof res !== 'string') {
return globalWeatherVar.value = DEFAULT_WEATHER; return (globalWeatherVar.value = DEFAULT_WEATHER);
} }
const parsedWeather = JSON.parse(res); const parsedWeather = JSON.parse(res);
if (Object.keys(parsedWeather).includes("error")) { if (Object.keys(parsedWeather).includes('error')) {
return globalWeatherVar.value = DEFAULT_WEATHER; return (globalWeatherVar.value = DEFAULT_WEATHER);
} }
return globalWeatherVar.value = parsedWeather; return (globalWeatherVar.value = parsedWeather);
} catch (error) { } catch (error) {
globalWeatherVar.value = DEFAULT_WEATHER; globalWeatherVar.value = DEFAULT_WEATHER;
console.warn(`Failed to parse weather data: ${error}`); 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}`); console.error(`Failed to fetch weather: ${err}`);
globalWeatherVar.value = DEFAULT_WEATHER; 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) { if (!weatherKey) {
return globalWeatherVar.value = DEFAULT_WEATHER; return (globalWeatherVar.value = DEFAULT_WEATHER);
} }
weatherIntervalFn(weatherInterval, loc, weatherKey); weatherIntervalFn(weatherInterval, loc, weatherKey);
}); });
export const getTemperature = (wthr: Weather, unt: UnitType) => { export const getTemperature = (wthr: Weather, unt: UnitType): string => {
if (unt === "imperial") { if (unt === 'imperial') {
return `${Math.ceil(wthr.current.temp_f)}° F`; return `${Math.ceil(wthr.current.temp_f)}° F`;
} else { } else {
return `${Math.ceil(wthr.current.temp_c)}° C`; return `${Math.ceil(wthr.current.temp_c)}° C`;
} }
}; };
export const getWeatherIcon = (fahren: number) => { export const getWeatherIcon = (fahren: number): Record<string, string> => {
const icons = { const icons = {
100: "", 100: '',
75: "", 75: '',
50: "", 50: '',
25: "", 25: '',
0: "", 0: '',
}; } as const;
const colors = { const colors = {
100: "weather-color red", 100: 'weather-color red',
75: "weather-color orange", 75: 'weather-color orange',
50: "weather-color lavender", 50: 'weather-color lavender',
25: "weather-color blue", 25: 'weather-color blue',
0: "weather-color sky", 0: 'weather-color sky',
}; } as const;
const threshold = type IconKeys = keyof typeof icons;
fahren < 0
? 0 const threshold: IconKeys =
: [100, 75, 50, 25, 0].find((threshold) => threshold <= fahren); 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 { return {
icon: icons[threshold || 50], icon,
color: colors[threshold || 50], color,
}; };
}; };
export const getWindConditions = (wthr: Weather, unt: UnitType) => { export const getWindConditions = (wthr: Weather, unt: UnitType): string => {
if (unt === "imperial") { if (unt === 'imperial') {
return `${Math.floor(wthr.current.wind_mph)} mph`; return `${Math.floor(wthr.current.wind_mph)} mph`;
} }
return `${Math.floor(wthr.current.wind_kph)} kph`; return `${Math.floor(wthr.current.wind_kph)} kph`;
}
export const getRainChance = (wthr: Weather) => `${wthr.forecast.forecastday[0].day.daily_chance_of_rain}%`;
export const 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 = { export const substitutes = {
"transmission-gtk": "transmission", 'transmission-gtk': 'transmission',
"blueberry.py": "blueberry", 'blueberry.py': 'blueberry',
"Caprine": "facebook-messenger", Caprine: 'facebook-messenger',
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic", 'com.raggesilver.BlackBox-symbolic': 'terminal-symbolic',
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic", 'org.wezfurlong.wezterm-symbolic': 'terminal-symbolic',
"audio-headset-bluetooth": "audio-headphones-symbolic", 'audio-headset-bluetooth': 'audio-headphones-symbolic',
"audio-card-analog-usb": "audio-speakers-symbolic", 'audio-card-analog-usb': 'audio-speakers-symbolic',
"audio-card-analog-pci": "audio-card-symbolic", 'audio-card-analog-pci': 'audio-card-symbolic',
"preferences-system": "emblem-system-symbolic", 'preferences-system': 'emblem-system-symbolic',
"com.github.Aylur.ags-symbolic": "controls-symbolic", 'com.github.Aylur.ags-symbolic': 'controls-symbolic',
"com.github.Aylur.ags": "controls-symbolic", 'com.github.Aylur.ags': 'controls-symbolic',
} } as const;
export default { export default {
missing: "image-missing-symbolic", missing: 'image-missing-symbolic',
nix: { nix: {
nix: "nix-snowflake-symbolic", nix: 'nix-snowflake-symbolic',
}, },
app: { app: {
terminal: "terminal-symbolic", terminal: 'terminal-symbolic',
}, },
fallback: { fallback: {
executable: "application-x-executable", executable: 'application-x-executable',
notification: "dialog-information-symbolic", notification: 'dialog-information-symbolic',
video: "video-x-generic-symbolic", video: 'video-x-generic-symbolic',
audio: "audio-x-generic-symbolic", audio: 'audio-x-generic-symbolic',
}, },
ui: { ui: {
close: "window-close-symbolic", close: 'window-close-symbolic',
colorpicker: "color-select-symbolic", colorpicker: 'color-select-symbolic',
info: "info-symbolic", info: 'info-symbolic',
link: "external-link-symbolic", link: 'external-link-symbolic',
lock: "system-lock-screen-symbolic", lock: 'system-lock-screen-symbolic',
menu: "open-menu-symbolic", menu: 'open-menu-symbolic',
refresh: "view-refresh-symbolic", refresh: 'view-refresh-symbolic',
search: "system-search-symbolic", search: 'system-search-symbolic',
settings: "emblem-system-symbolic", settings: 'emblem-system-symbolic',
themes: "preferences-desktop-theme-symbolic", themes: 'preferences-desktop-theme-symbolic',
tick: "object-select-symbolic", tick: 'object-select-symbolic',
time: "hourglass-symbolic", time: 'hourglass-symbolic',
toolbars: "toolbars-symbolic", toolbars: 'toolbars-symbolic',
warning: "dialog-warning-symbolic", warning: 'dialog-warning-symbolic',
avatar: "avatar-default-symbolic", avatar: 'avatar-default-symbolic',
arrow: { arrow: {
right: "pan-end-symbolic", right: 'pan-end-symbolic',
left: "pan-start-symbolic", left: 'pan-start-symbolic',
down: "pan-down-symbolic", down: 'pan-down-symbolic',
up: "pan-up-symbolic", up: 'pan-up-symbolic',
}, },
}, },
audio: { audio: {
mic: { mic: {
muted: "microphone-disabled-symbolic", muted: 'microphone-disabled-symbolic',
low: "microphone-sensitivity-low-symbolic", low: 'microphone-sensitivity-low-symbolic',
medium: "microphone-sensitivity-medium-symbolic", medium: 'microphone-sensitivity-medium-symbolic',
high: "microphone-sensitivity-high-symbolic", high: 'microphone-sensitivity-high-symbolic',
}, },
volume: { volume: {
muted: "audio-volume-muted-symbolic", muted: 'audio-volume-muted-symbolic',
low: "audio-volume-low-symbolic", low: 'audio-volume-low-symbolic',
medium: "audio-volume-medium-symbolic", medium: 'audio-volume-medium-symbolic',
high: "audio-volume-high-symbolic", high: 'audio-volume-high-symbolic',
overamplified: "audio-volume-overamplified-symbolic", overamplified: 'audio-volume-overamplified-symbolic',
}, },
type: { type: {
headset: "audio-headphones-symbolic", headset: 'audio-headphones-symbolic',
speaker: "audio-speakers-symbolic", speaker: 'audio-speakers-symbolic',
card: "audio-card-symbolic", card: 'audio-card-symbolic',
}, },
mixer: "mixer-symbolic", mixer: 'mixer-symbolic',
}, },
powerprofile: { powerprofile: {
balanced: "power-profile-balanced-symbolic", balanced: 'power-profile-balanced-symbolic',
"power-saver": "power-profile-power-saver-symbolic", 'power-saver': 'power-profile-power-saver-symbolic',
performance: "power-profile-performance-symbolic", performance: 'power-profile-performance-symbolic',
}, },
asusctl: { asusctl: {
profile: { profile: {
Balanced: "power-profile-balanced-symbolic", Balanced: 'power-profile-balanced-symbolic',
Quiet: "power-profile-power-saver-symbolic", Quiet: 'power-profile-power-saver-symbolic',
Performance: "power-profile-performance-symbolic", Performance: 'power-profile-performance-symbolic',
}, },
mode: { mode: {
Integrated: "processor-symbolic", Integrated: 'processor-symbolic',
Hybrid: "controller-symbolic", Hybrid: 'controller-symbolic',
}, },
}, },
battery: { battery: {
charging: "battery-flash-symbolic", charging: 'battery-flash-symbolic',
warning: "battery-empty-symbolic", warning: 'battery-empty-symbolic',
}, },
bluetooth: { bluetooth: {
enabled: "bluetooth-active-symbolic", enabled: 'bluetooth-active-symbolic',
disabled: "bluetooth-disabled-symbolic", disabled: 'bluetooth-disabled-symbolic',
}, },
brightness: { brightness: {
indicator: "display-brightness-symbolic", indicator: 'display-brightness-symbolic',
keyboard: "keyboard-brightness-symbolic", keyboard: 'keyboard-brightness-symbolic',
screen: "display-brightness-symbolic", screen: 'display-brightness-symbolic',
}, },
powermenu: { powermenu: {
sleep: "weather-clear-night-symbolic", sleep: 'weather-clear-night-symbolic',
reboot: "system-reboot-symbolic", reboot: 'system-reboot-symbolic',
logout: "system-log-out-symbolic", logout: 'system-log-out-symbolic',
shutdown: "system-shutdown-symbolic", shutdown: 'system-shutdown-symbolic',
}, },
recorder: { recorder: {
recording: "media-record-symbolic", recording: 'media-record-symbolic',
}, },
notifications: { notifications: {
noisy: "org.gnome.Settings-notifications-symbolic", noisy: 'org.gnome.Settings-notifications-symbolic',
silent: "notifications-disabled-symbolic", silent: 'notifications-disabled-symbolic',
message: "chat-bubbles-symbolic", message: 'chat-bubbles-symbolic',
}, },
trash: { trash: {
full: "user-trash-full-symbolic", full: 'user-trash-full-symbolic',
empty: "user-trash-symbolic", empty: 'user-trash-symbolic',
}, },
mpris: { mpris: {
shuffle: { shuffle: {
enabled: "media-playlist-shuffle-symbolic", enabled: 'media-playlist-shuffle-symbolic',
disabled: "media-playlist-consecutive-symbolic", disabled: 'media-playlist-consecutive-symbolic',
}, },
loop: { loop: {
none: "media-playlist-repeat-symbolic", none: 'media-playlist-repeat-symbolic',
track: "media-playlist-repeat-song-symbolic", track: 'media-playlist-repeat-song-symbolic',
playlist: "media-playlist-repeat-symbolic", playlist: 'media-playlist-repeat-symbolic',
}, },
playing: "media-playback-pause-symbolic", playing: 'media-playback-pause-symbolic',
paused: "media-playback-start-symbolic", paused: 'media-playback-start-symbolic',
stopped: "media-playback-start-symbolic", stopped: 'media-playback-start-symbolic',
prev: "media-skip-backward-symbolic", prev: 'media-skip-backward-symbolic',
next: "media-skip-forward-symbolic", next: 'media-skip-forward-symbolic',
}, },
system: { system: {
cpu: "org.gnome.SystemMonitor-symbolic", cpu: 'org.gnome.SystemMonitor-symbolic',
ram: "drive-harddisk-solidstate-symbolic", ram: 'drive-harddisk-solidstate-symbolic',
temp: "temperature-symbolic", temp: 'temperature-symbolic',
}, },
color: { color: {
dark: "dark-mode-symbolic", dark: 'dark-mode-symbolic',
light: "light-mode-symbolic", light: 'light-mode-symbolic',
}, },
} };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

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; index: number;
uuid: string; uuid: string;
name: string; name: string;
"temperature.gpu": number; 'temperature.gpu': number;
"fan.speed": number; 'fan.speed': number;
"utilization.gpu": number; 'utilization.gpu': number;
"utilization.enc": number; 'utilization.enc': number;
"utilization.dec": number; 'utilization.dec': number;
"power.draw": number; 'power.draw': number;
"enforced.power.limit": number; 'enforced.power.limit': number;
"memory.used": number; 'memory.used': number;
"memory.total": number; 'memory.total': number;
processes: Process[]; processes: Process[];
}; };

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 = { export type AccessPoint = {
bssid: string | null; bssid: string | null;
address: string | null; address: string | null;
@@ -7,4 +9,8 @@ export type AccessPoint = {
strength: number; strength: number;
frequency: number; frequency: number;
iconName: string | undefined; iconName: string | undefined;
} };
export type WifiStatus = keyof typeof WIFI_STATUS_MAP;
export type WifiIcon = '󰤩' | '󰤨' | '󰤪' | '󰤨' | '󰤩' | '󰤮' | '󰤨' | '󰤥' | '󰤢' | '󰤟' | '󰤯';

View File

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

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 = { export type Weather = {
location: Location; location: Location;
current: Current; current: Current;
forecast: Forecast; forecast: Forecast;
} };
export type Current = { export type Current = {
last_updated_epoch?: number; last_updated_epoch?: number;
@@ -43,17 +45,17 @@ export type Current = {
chance_of_rain?: number; chance_of_rain?: number;
will_it_snow?: number; will_it_snow?: number;
chance_of_snow?: number; chance_of_snow?: number;
} };
export type Condition = { export type Condition = {
text: string; text: string;
icon: string; icon: string;
code: number; code: number;
} };
export type Forecast = { export type Forecast = {
forecastday: Forecastday[]; forecastday: Forecastday[];
} };
export type Forecastday = { export type Forecastday = {
date: string; date: string;
@@ -61,7 +63,7 @@ export type Forecastday = {
day: Day; day: Day;
astro: Astro; astro: Astro;
hour: Current[]; hour: Current[];
} };
export type Astro = { export type Astro = {
sunrise: string; sunrise: string;
@@ -72,7 +74,7 @@ export type Astro = {
moon_illumination: number; moon_illumination: number;
is_moon_up: number; is_moon_up: number;
is_sun_up: number; is_sun_up: number;
} };
export type Day = { export type Day = {
maxtemp_c: number; maxtemp_c: number;
@@ -95,7 +97,7 @@ export type Day = {
daily_chance_of_snow: number; daily_chance_of_snow: number;
condition: Condition; condition: Condition;
uv: number; uv: number;
} };
export type Location = { export type Location = {
name: string; name: string;
@@ -106,4 +108,11 @@ export type Location = {
tz_id: string; tz_id: string;
localtime_epoch: number; localtime_epoch: number;
localtime: string; 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 Exclusivity = 'normal' | 'ignore' | 'exclusive';
export type Anchor = "left" | "right" | "top" | "down"; export type Anchor = 'left' | 'right' | 'top' | 'down';
export type Transition = "none" | "crossfade" | "slide_right" | "slide_left" | "slide_up" | "slide_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 = { export type WorkspaceRule = {
workspaceString: string, workspaceString: string;
monitor: string, monitor: string;
} };
export type WorkspaceMap = { export type WorkspaceMap = {
[key: string]: number[], [key: string]: number[];
} };
export type MonitorMap = {
[key: number]: string;
};
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 */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { type Application } from "types/service/applications" import { type Application } from 'types/service/applications';
import { NotificationAnchor } from "./types/options" import { NotificationAnchor } from './types/options';
import { OSDAnchor } from "lib/types/options"; import { OSDAnchor } from 'lib/types/options';
import icons, { substitutes } from "./icons" import icons, { substitutes } from './icons';
import Gtk from "gi://Gtk?version=3.0" import Gtk from 'gi://Gtk?version=3.0';
import Gdk from "gi://Gdk" import Gdk from 'gi://Gdk';
import GLib from "gi://GLib?version=2.0" import GLib from 'gi://GLib?version=2.0';
import GdkPixbuf from "gi://GdkPixbuf"; import GdkPixbuf from 'gi://GdkPixbuf';
import { NotificationArgs } from "types/utils/notify" import { NotificationArgs } from 'types/utils/notify';
import { SubstituteKeys } from './types/utils';
import { 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 * @returns substitute icon || name || fallback icon
*/ */
export function icon(name: string | null, fallback = icons.missing) { export function icon(name: string | null, fallback = icons.missing): string {
if (!name) const validateSubstitute = (name: string): name is SubstituteKeys => name in substitutes;
return fallback || ""
if (GLib.file_test(name, GLib.FileTest.EXISTS)) if (!name) return fallback || '';
return name
const icon = (substitutes[name] || name) if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
if (Utils.lookUpIcon(icon))
return icon
print(`no icon substitute "${icon}" for "${name}", fallback: "${fallback}"`) let icon: string = name;
return fallback
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]) * @returns execAsync(["bash", "-c", cmd])
*/ */
export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]) { export async function bash(strings: TemplateStringsArray | string, ...values: unknown[]): Promise<string> {
const cmd = typeof strings === "string" ? strings : strings const cmd =
.flatMap((str, i) => str + `${values[i] ?? ""}`) typeof strings === 'string' ? strings : strings.flatMap((str, i) => str + `${values[i] ?? ''}`).join('');
.join("")
return Utils.execAsync(["bash", "-c", cmd]).catch(err => { return Utils.execAsync(['bash', '-c', cmd]).catch((err) => {
console.error(cmd, err) console.error(cmd, err);
return "" return '';
}) });
} }
/** /**
* @returns execAsync(cmd) * @returns execAsync(cmd)
*/ */
export async function sh(cmd: string | string[]) { export async function sh(cmd: string | string[]): Promise<string> {
return Utils.execAsync(cmd).catch(err => { return Utils.execAsync(cmd).catch((err) => {
console.error(typeof cmd === "string" ? cmd : cmd.join(" "), err) console.error(typeof cmd === 'string' ? cmd : cmd.join(' '), err);
return "" return '';
}) });
} }
export function forMonitors(widget: (monitor: number) => Gtk.Window) { export function forMonitors(widget: (monitor: number) => Gtk.Window): Window[] {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1; const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).flatMap(widget); return range(n, 0).flatMap(widget);
} }
@@ -61,64 +67,62 @@ export function forMonitors(widget: (monitor: number) => Gtk.Window) {
/** /**
* @returns [start...length] * @returns [start...length]
*/ */
export function range(length: number, start = 1) { export function range(length: number, start = 1): number[] {
return Array.from({ length }, (_, i) => i + start) return Array.from({ length }, (_, i) => i + start);
} }
/** /**
* @returns true if all of the `bins` are found * @returns true if all of the `bins` are found
*/ */
export function dependencies(...bins: string[]) { export function dependencies(...bins: string[]): boolean {
const missing = bins.filter(bin => Utils.exec({ const missing = bins.filter((bin) =>
cmd: `which ${bin}`, Utils.exec({
out: () => false, cmd: `which ${bin}`,
err: () => true, out: () => false,
})) err: () => true,
}),
);
if (missing.length > 0) { if (missing.length > 0) {
console.warn(Error(`missing dependencies: ${missing.join(", ")}`)) console.warn(Error(`missing dependencies: ${missing.join(', ')}`));
Notify({ Notify({
summary: "Dependencies not found!", summary: 'Dependencies not found!',
body: `The following dependencies are missing: ${missing.join(", ")}`, body: `The following dependencies are missing: ${missing.join(', ')}`,
iconName: icons.ui.warning, iconName: icons.ui.warning,
timeout: 7000 timeout: 7000,
}); });
} }
return missing.length === 0 return missing.length === 0;
} }
/** /**
* run app detached * run app detached
*/ */
export function launchApp(app: Application) { export function launchApp(app: Application): void {
const exe = app.executable const exe = app.executable
.split(/\s+/) .split(/\s+/)
.filter(str => !str.startsWith("%") && !str.startsWith("@")) .filter((str) => !str.startsWith('%') && !str.startsWith('@'))
.join(" ") .join(' ');
bash(`${exe} &`) bash(`${exe} &`);
app.frequency += 1 app.frequency += 1;
} }
/** /**
* to use with drag and drop * to use with drag and drop
*/ */
export function createSurfaceFromWidget(widget: Gtk.Widget) { export function createSurfaceFromWidget(widget: Gtk.Widget): GdkPixbuf.Pixbuf {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const cairo = imports.gi.cairo as any const cairo = imports.gi.cairo as any;
const alloc = widget.get_allocation() const alloc = widget.get_allocation();
const surface = new cairo.ImageSurface( const surface = new cairo.ImageSurface(cairo.Format.ARGB32, alloc.width, alloc.height);
cairo.Format.ARGB32, const cr = new cairo.Context(surface);
alloc.width, cr.setSourceRGBA(255, 255, 255, 0);
alloc.height, cr.rectangle(0, 0, alloc.width, alloc.height);
) cr.fill();
const cr = new cairo.Context(surface) widget.draw(cr);
cr.setSourceRGBA(255, 255, 255, 0) return surface;
cr.rectangle(0, 0, alloc.width, alloc.height)
cr.fill()
widget.draw(cr)
return surface
} }
/** /**
@@ -129,9 +133,10 @@ export const isAnImage = (imgFilePath: string): boolean => {
GdkPixbuf.Pixbuf.new_from_file(imgFilePath); GdkPixbuf.Pixbuf.new_from_file(imgFilePath);
return true; return true;
} catch (error) { } catch (error) {
console.error(error);
return false; return false;
} }
} };
export const Notify = (notifPayload: NotificationArgs): void => { export const Notify = (notifPayload: NotificationArgs): void => {
let command = 'notify-send'; let command = 'notify-send';
@@ -145,20 +150,42 @@ export const Notify = (notifPayload: NotificationArgs): void => {
if (notifPayload.transient) command += ` -e`; if (notifPayload.transient) command += ` -e`;
if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`; if (notifPayload.id !== undefined) command += ` -r ${notifPayload.id}`;
Utils.execAsync(command) Utils.execAsync(command);
} };
export function getPosition(pos: NotificationAnchor | OSDAnchor): ("top" | "bottom" | "left" | "right")[] { export function getPosition(pos: NotificationAnchor | OSDAnchor): ('top' | 'bottom' | 'left' | 'right')[] {
const positionMap: { [key: string]: ("top" | "bottom" | "left" | "right")[] } = { const positionMap: { [key: string]: ('top' | 'bottom' | 'left' | 'right')[] } = {
"top": ["top"], top: ['top'],
"top right": ["top", "right"], 'top right': ['top', 'right'],
"top left": ["top", "left"], 'top left': ['top', 'left'],
"bottom": ["bottom"], bottom: ['bottom'],
"bottom right": ["bottom", "right"], 'bottom right': ['bottom', 'right'],
"bottom left": ["bottom", "left"], 'bottom left': ['bottom', 'left'],
"right": ["right"], right: ['right'],
"left": ["left"], left: ['left'],
}; };
return positionMap[pos] || ["top"]; return positionMap[pos] || ['top'];
} }
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(), { export const clock = Variable(GLib.DateTime.new_now_local(), {
poll: [1000, () => GLib.DateTime.new_now_local()], poll: [1000, (): DateTime => GLib.DateTime.new_now_local()],
}) });
export const uptime = Variable(0, { export const uptime = Variable(0, {
poll: [60_000, "cat /proc/uptime", line => poll: [60_000, 'cat /proc/uptime', (line): number => Number.parseInt(line.split('.')[0]) / 60],
Number.parseInt(line.split(".")[0]) / 60, });
],
})
export const distro = { export const distro = {
id: GLib.get_os_info("ID"), id: GLib.get_os_info('ID'),
logo: GLib.get_os_info("LOGO"), logo: GLib.get_os_info('LOGO'),
} };

30
main.ts
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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