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

* Implemented strict linting standards and prettier formatting config.

* More linter fixes and type updates.

* More linter updates and type fixes

* Remove noisy comments

* Linter and type updates

* Linter, formatting and type updates.

* Linter updates

* Type updates

* Type updates

* fixed all linter errors

* Fixed all linting, formatting and type issues.

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

View File

@@ -1,8 +1,10 @@
const hyprland = await Service.import("hyprland");
const hyprland = await Service.import('hyprland');
import {
Menu,
Workspaces, ClientTitle, Media,
Workspaces,
ClientTitle,
Media,
Notifications,
Volume,
Network,
@@ -20,54 +22,57 @@ import {
Updates,
Weather,
Power,
} from "./Exports"
} from './Exports';
import { BarItemBox as WidgetContainer } from "../shared/barItemBox.js";
import options from "options";
import Gdk from "gi://Gdk?version=3.0";
import Button from "types/widgets/button.js";
import Gtk from "types/@girs/gtk-3.0/gtk-3.0.js";
import { BarItemBox as WidgetContainer } from '../shared/barItemBox.js';
import options from 'options';
import Gdk from 'gi://Gdk?version=3.0';
import Button from 'types/widgets/button.js';
import Gtk from 'types/@girs/gtk-3.0/gtk-3.0.js';
import './SideEffects';
import { WindowLayer } from "lib/types/options.js";
import { 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;
export type BarWidget = keyof typeof widget;
type Section = "battery"
| "dashboard"
| "workspaces"
| "windowtitle"
| "media"
| "notifications"
| "volume"
| "network"
| "bluetooth"
| "clock"
| "ram"
| "cpu"
| "storage"
| "netstat"
| "kbinput"
| "updates"
| "weather"
| "power"
| "systray";
type Section =
| 'battery'
| 'dashboard'
| 'workspaces'
| 'windowtitle'
| 'media'
| 'notifications'
| 'volume'
| 'network'
| 'bluetooth'
| 'clock'
| 'ram'
| 'cpu'
| 'storage'
| 'netstat'
| 'kbinput'
| 'updates'
| 'weather'
| 'power'
| 'systray';
type Layout = {
left: Section[],
middle: Section[],
right: Section[],
}
left: Section[];
middle: Section[];
right: Section[];
};
type BarLayout = {
[key: string]: Layout
}
[key: string]: Layout;
};
const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
const matchingKey = Object.keys(layouts).find(key => key === monitor.toString());
const wildcard = Object.keys(layouts).find(key => key === "*");
const matchingKey = Object.keys(layouts).find((key) => key === monitor.toString());
const wildcard = Object.keys(layouts).find((key) => key === '*');
if (matchingKey) {
return layouts[matchingKey];
@@ -77,62 +82,48 @@ const getLayoutForMonitor = (monitor: number, layouts: BarLayout): Layout => {
return layouts[wildcard];
}
return {
left: [
"dashboard",
"workspaces",
"windowtitle"
],
middle: [
"media"
],
right: [
"volume",
"network",
"bluetooth",
"battery",
"systray",
"clock",
"notifications"
]
return {
left: ['dashboard', 'workspaces', 'windowtitle'],
middle: ['media'],
right: ['volume', 'network', 'bluetooth', 'battery', 'systray', 'clock', 'notifications'],
};
}
};
const widget = {
battery: () => WidgetContainer(BatteryLabel()),
dashboard: () => WidgetContainer(Menu()),
workspaces: (monitor: number) => WidgetContainer(Workspaces(monitor)),
windowtitle: () => WidgetContainer(ClientTitle()),
media: () => WidgetContainer(Media()),
notifications: () => WidgetContainer(Notifications()),
volume: () => WidgetContainer(Volume()),
network: () => WidgetContainer(Network()),
bluetooth: () => WidgetContainer(Bluetooth()),
clock: () => WidgetContainer(Clock()),
systray: () => WidgetContainer(SysTray()),
ram: () => WidgetContainer(Ram()),
cpu: () => WidgetContainer(Cpu()),
storage: () => WidgetContainer(Storage()),
netstat: () => WidgetContainer(Netstat()),
kbinput: () => WidgetContainer(KbInput()),
updates: () => WidgetContainer(Updates()),
weather: () => WidgetContainer(Weather()),
power: () => WidgetContainer(Power()),
battery: (): Button<Child, Attribute> => WidgetContainer(BatteryLabel()),
dashboard: (): Button<Child, Attribute> => WidgetContainer(Menu()),
workspaces: (monitor: number): Button<Child, Attribute> => WidgetContainer(Workspaces(monitor)),
windowtitle: (): Button<Child, Attribute> => WidgetContainer(ClientTitle()),
media: (): Button<Child, Attribute> => WidgetContainer(Media()),
notifications: (): Button<Child, Attribute> => WidgetContainer(Notifications()),
volume: (): Button<Child, Attribute> => WidgetContainer(Volume()),
network: (): Button<Child, Attribute> => WidgetContainer(Network()),
bluetooth: (): Button<Child, Attribute> => WidgetContainer(Bluetooth()),
clock: (): Button<Child, Attribute> => WidgetContainer(Clock()),
systray: (): Button<Child, Attribute> => WidgetContainer(SysTray()),
ram: (): Button<Child, Attribute> => WidgetContainer(Ram()),
cpu: (): Button<Child, Attribute> => WidgetContainer(Cpu()),
storage: (): Button<Child, Attribute> => WidgetContainer(Storage()),
netstat: (): Button<Child, Attribute> => WidgetContainer(Netstat()),
kbinput: (): Button<Child, Attribute> => WidgetContainer(KbInput()),
updates: (): Button<Child, Attribute> => WidgetContainer(Updates()),
weather: (): Button<Child, Attribute> => WidgetContainer(Weather()),
power: (): Button<Child, Attribute> => WidgetContainer(Power()),
};
type GdkMonitors = {
[key: string]: {
key: string,
model: string,
used: boolean
}
key: string;
model: string;
used: boolean;
};
};
function getGdkMonitors(): GdkMonitors {
const display = Gdk.Display.get_default();
if (display === null) {
console.error("Failed to get Gdk display.");
console.error('Failed to get Gdk display.');
return {};
}
@@ -162,35 +153,35 @@ function getGdkMonitors(): GdkMonitors {
* NOTE: Some more funky stuff being done by GDK.
* We render windows/bar based on the monitor ID. So if you have 3 monitors, then your
* monitor IDs will be [0, 1, 2]. Hyprland will NEVER change what ID belongs to what monitor.
*
*
* So if hyprland determines id 0 = DP-1, even after you unplug, shut off or restart your monitor,
* the id 0 will ALWAYS be DP-1.
*
*
* However, GDK (the righteous genius that it is) will change the order of ID anytime your monitor
* setup is changed. So if you unplug your monitor and plug it back it, it now becomes the last id.
* So if DP-1 was id 0 and you unplugged it, it will reconfigure to id 2. This sucks because now
* there's a mismtach between what GDK determines the monitor is at id 2 and what Hyprland determines
* is at id 2.
*
*
* So for that reason, we need to redirect the input `monitor` that the Bar module takes in, to the
* proper Hyprland monitor. So when monitor id 0 comes in, we need to find what the id of that monitor
* is being determined as by Hyprland so the bars show up on the right monitors.
*
*
* Since GTK3 doesn't contain connection names and only monitor models, we have to make the best guess
* in the case that there are multiple models in the same resolution with the same scale. We find the
* 'right' monitor by checking if the model matches along with the resolution and scale. If monitor at
* ID 0 for GDK is being reported as 'MSI MAG271CQR' we find the same model in the Hyprland monitor list
* and check if the resolution and scaling is the same... if it is then we determine it's a match.
*
*
* The edge-case that we just can't handle is if you have the same monitors in the same resolution at the same
* scale. So if you've got 2 'MSI MAG271CQR' monitors at 2560x1440 at scale 1, then we just match the first
* monitor in the list as the first match and then the second 'MSI MAG271CQR' as a match in the 2nd iteration.
* You may have the bar showing up on the wrong one in this case because we don't know what the connector id
* monitor in the list as the first match and then the second 'MSI MAG271CQR' as a match in the 2nd iteration.
* You may have the bar showing up on the wrong one in this case because we don't know what the connector id
* is of either of these monitors (DP-1, DP-2) which are unique values - as these are only in GTK4.
*
*
* Keep in mind though, this is ONLY an issue if you change your monitor setup by plugging in a new one, restarting
* an existing one or shutting it off.
*
*
* If your monitors aren't changed in the current session you're in then none of this safeguarding is relevant.
*
* Fun stuff really... :facepalm:
@@ -200,7 +191,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitors = getGdkMonitors();
if (Object.keys(gdkMonitors).length === 0) {
console.error("No GDK monitors were found.");
console.error('No GDK monitors were found.');
return monitor;
}
@@ -208,7 +199,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
const gdkMonitor = gdkMonitors[monitor];
// First pass: Strict matching including the monitor index (i.e., hypMon.id === monitor + resolution+scale criteria)
const directMatch = hyprland.monitors.find(hypMon => {
const directMatch = hyprland.monitors.find((hypMon) => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
});
@@ -219,7 +210,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
}
// Second pass: Relaxed matching without considering the monitor index
const hyprlandMonitor = hyprland.monitors.find(hypMon => {
const hyprlandMonitor = hyprland.monitors.find((hypMon) => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id);
});
@@ -230,7 +221,7 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
}
// Fallback: Find the first available monitor ID that hasn't been used
const fallbackMonitor = hyprland.monitors.find(hypMon => !usedHyprlandMonitors.has(hypMon.id));
const fallbackMonitor = hyprland.monitors.find((hypMon) => !usedHyprlandMonitors.has(hypMon.id));
if (fallbackMonitor) {
usedHyprlandMonitors.add(fallbackMonitor.id);
@@ -253,62 +244,63 @@ const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<num
export const Bar = (() => {
const usedHyprlandMonitors = new Set<number>();
return (monitor: number) => {
return (monitor: number): Window<Child, Attribute> => {
const hyprlandMonitor = gdkMonitorIdToHyprlandId(monitor, usedHyprlandMonitors);
return Widget.Window({
name: `bar-${hyprlandMonitor}`,
class_name: "bar",
class_name: 'bar',
monitor,
visible: true,
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
anchor: ['top', 'left', 'right'],
exclusivity: 'exclusive',
layer: Utils.merge(
[
options.theme.bar.layer.bind("value"),
options.tear.bind("value")
],
(
barLayer: WindowLayer,
tear: boolean
) => {
if (tear && barLayer === "overlay") {
return "top";
[options.theme.bar.layer.bind('value'), options.tear.bind('value')],
(barLayer: WindowLayer, tear: boolean) => {
if (tear && barLayer === 'overlay') {
return 'top';
}
return barLayer;
}),
},
),
child: Widget.Box({
class_name: 'bar-panel-container',
child: Widget.CenterBox({
class_name: 'bar-panel',
css: 'padding: 1px',
startWidget: Widget.Box({
class_name: "box-left",
class_name: 'box-left',
hexpand: true,
setup: self => {
setup: (self) => {
self.hook(layouts, (self) => {
const foundLayout = 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({
class_name: "box-center",
hpack: "center",
setup: self => {
class_name: 'box-center',
hpack: 'center',
setup: (self) => {
self.hook(layouts, (self) => {
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({
class_name: "box-right",
hpack: "end",
setup: self => {
class_name: 'box-right',
hpack: 'end',
setup: (self) => {
self.hook(layouts, (self) => {
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 { Workspaces } from "./workspaces/index";
import { ClientTitle } from "./window_title/index";
import { Media } from "./media/index";
import { Notifications } from "./notifications/index";
import { Volume } from "./volume/index";
import { Network } from "./network/index";
import { Bluetooth } from "./bluetooth/index";
import { BatteryLabel } from "./battery/index";
import { Clock } from "./clock/index";
import { SysTray } from "./systray/index";
import { Menu } from './menu/index';
import { Workspaces } from './workspaces/index';
import { ClientTitle } from './window_title/index';
import { Media } from './media/index';
import { Notifications } from './notifications/index';
import { Volume } from './volume/index';
import { Network } from './network/index';
import { Bluetooth } from './bluetooth/index';
import { BatteryLabel } from './battery/index';
import { Clock } from './clock/index';
import { SysTray } from './systray/index';
// Custom Modules
import { Ram } from "../../customModules/ram/index";
import { Cpu } from "../../customModules/cpu/index";
import { Storage } from "customModules/storage/index";
import { Netstat } from "customModules/netstat/index";
import { KbInput } from "customModules/kblayout/index";
import { Updates } from "customModules/updates/index";
import { Weather } from "customModules/weather/index";
import { Power } from "customModules/power/index";
import { Ram } from '../../customModules/ram/index';
import { Cpu } from '../../customModules/cpu/index';
import { Storage } from 'customModules/storage/index';
import { Netstat } from 'customModules/netstat/index';
import { KbInput } from 'customModules/kblayout/index';
import { Updates } from 'customModules/updates/index';
import { Weather } from 'customModules/weather/index';
import { Power } from 'customModules/power/index';
export {
Menu,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,42 +1,54 @@
const hyprland = await Service.import("hyprland");
import options from "options";
import { createThrottledScrollHandlers, getCurrentMonitorWorkspaces, getWorkspaceRules, getWorkspacesForMonitor } from "./helpers";
import { Workspace } from "types/service/hyprland";
const hyprland = await Service.import('hyprland');
import options from 'options';
import {
createThrottledScrollHandlers,
getCurrentMonitorWorkspaces,
getWorkspaceRules,
getWorkspacesForMonitor,
} from './helpers';
import { Workspace } from 'types/service/hyprland';
import { BoxWidget } from 'lib/types/widget';
import { BarBoxChild, SelfButton } from 'lib/types/bar';
const {
workspaces,
monitorSpecific,
workspaceMask,
scroll_speed,
spacing
} = options.bar.workspaces;
const { workspaces, monitorSpecific, workspaceMask, scroll_speed, spacing } = options.bar.workspaces;
function range(length: number, start = 1) {
function range(length: number, start = 1): number[] {
return Array.from({ length }, (_, i) => i + start);
}
const Workspaces = (monitor = -1) => {
const Workspaces = (monitor = -1): BarBoxChild => {
const currentMonitorWorkspaces = Variable(getCurrentMonitorWorkspaces(monitor));
workspaces.connect("changed", () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor)
})
workspaces.connect('changed', () => {
currentMonitorWorkspaces.value = getCurrentMonitorWorkspaces(monitor);
});
const renderClassnames = (showIcons: boolean, showNumbered: boolean, numberedActiveIndicator: string, i: number) => {
const renderClassnames = (
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
i: number,
): string => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i
? numberedActiveIndicator
: "";
const numActiveInd = hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return "default";
}
return 'default';
};
const renderLabel = (showIcons: boolean, available: string, active: string, occupied: string, workspaceMask: boolean, i: number, index: number) => {
const renderLabel = (
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
i: number,
index: number,
): string => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
@@ -44,20 +56,16 @@ const Workspaces = (monitor = -1) => {
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (
monitor !== -1
) {
if (monitor !== -1) {
return available;
}
}
return workspaceMask
? `${index + 1}`
: `${i}`;
}
const defaultWses = () => {
return workspaceMask ? `${index + 1}` : `${i}`;
};
const defaultWses = (): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[workspaces.bind("value"), monitorSpecific.bind()],
[workspaces.bind('value'), monitorSpecific.bind()],
(workspaces: number, monitorSpecific: boolean) => {
return range(workspaces || 8)
.filter((i) => {
@@ -72,49 +80,57 @@ const Workspaces = (monitor = -1) => {
})
.map((i, index) => {
return Widget.Button({
class_name: "workspace-button",
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`)
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: "center",
css: spacing.bind("value").as(sp => `margin: 0rem ${0.375 * sp}rem;`),
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")
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) => {
(
showIcons: boolean,
showNumbered: boolean,
numberedActiveIndicator: string,
) => {
if (showIcons) {
return `workspace-icon txt-icon bar`;
}
if (showNumbered) {
const numActiveInd = hyprland.active.workspace.id === i
? numberedActiveIndicator
: "";
const numActiveInd =
hyprland.active.workspace.id === i ? numberedActiveIndicator : '';
return `workspace-number can_${numberedActiveIndicator} ${numActiveInd}`;
}
return "default";
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")
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) => {
(
showIcons: boolean,
available: string,
active: string,
occupied: string,
workspaceMask: boolean,
) => {
if (showIcons) {
if (hyprland.active.workspace.id === i) {
return active;
@@ -122,52 +138,45 @@ const Workspaces = (monitor = -1) => {
if ((hyprland.getWorkspace(i)?.windows || 0) > 0) {
return occupied;
}
if (
monitor !== -1
) {
if (monitor !== -1) {
return available;
}
}
return workspaceMask
? `${index + 1}`
: `${i}`;
return workspaceMask ? `${index + 1}` : `${i}`;
},
),
setup: (self) => {
self.hook(hyprland, () => {
self.toggleClassName('active', hyprland.active.workspace.id === i);
self.toggleClassName(
"active",
hyprland.active.workspace.id === i,
);
self.toggleClassName(
"occupied",
'occupied',
(hyprland.getWorkspace(i)?.windows || 0) > 0,
);
});
},
})
}),
});
});
},
)
})
}
const occupiedWses = () => {
),
});
};
const occupiedWses = (): BoxWidget => {
return Widget.Box({
children: Utils.merge(
[
monitorSpecific.bind("value"),
hyprland.bind("workspaces"),
workspaceMask.bind("value"),
workspaces.bind("value"),
options.bar.workspaces.show_icons.bind("value"),
options.bar.workspaces.icons.available.bind("value"),
options.bar.workspaces.icons.active.bind("value"),
options.bar.workspaces.icons.occupied.bind("value"),
options.bar.workspaces.show_numbered.bind("value"),
options.bar.workspaces.numbered_active_indicator.bind("value"),
spacing.bind("value"),
hyprland.active.workspace.bind("id"),
monitorSpecific.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,
@@ -185,29 +194,37 @@ const Workspaces = (monitor = -1) => {
) => {
let allWkspcs = range(totalWkspcs || 8);
const activeWorkspaces = wkSpaces.map(w => w.id);
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);
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)) {
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 => {
const wrkspcsInRange = range(totalWkspcs).filter((w) => {
return getWorkspacesForMonitor(w, workspaceRules, monitor);
});
allWkspcs = [...new Set([...activesForMonitor, ...wrkspcsInRange])];
@@ -221,51 +238,53 @@ const Workspaces = (monitor = -1) => {
})
.map((i, index) => {
return Widget.Button({
class_name: "workspace-button",
class_name: 'workspace-button',
on_primary_click: () => {
hyprland.messageAsync(`dispatch workspace ${i}`)
hyprland.messageAsync(`dispatch workspace ${i}`);
},
child: Widget.Label({
attribute: i,
vpack: "center",
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,
);
self.toggleClassName('active', activeId === i);
self.toggleClassName('occupied', (hyprland.getWorkspace(i)?.windows || 0) > 0);
},
})
}),
});
});
},
)
})
}
),
});
};
return {
component: Widget.Box({
class_name: "workspaces",
child: options.bar.workspaces.hideUnoccupied.bind("value").as(hideUnoccupied => hideUnoccupied ? occupiedWses() : defaultWses()),
class_name: 'workspaces',
child: options.bar.workspaces.hideUnoccupied
.bind('value')
.as((hideUnoccupied) => (hideUnoccupied ? occupiedWses() : defaultWses())),
}),
isVisible: true,
boxClass: "workspaces",
boxClass: 'workspaces',
props: {
setup: (self: any) => {
Utils.merge([scroll_speed.bind("value"), options.bar.workspaces.hideUnoccupied.bind("value")], (scroll_speed, hideUnoccupied) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(scroll_speed, currentMonitorWorkspaces, hideUnoccupied)
self.on_scroll_up = throttledScrollUp;
self.on_scroll_down = throttledScrollDown;
});
}
}
setup: (self: SelfButton): void => {
Utils.merge(
[scroll_speed.bind('value'), options.bar.workspaces.hideUnoccupied.bind('value')],
(scroll_speed, hideUnoccupied) => {
const { throttledScrollUp, throttledScrollDown } = createThrottledScrollHandlers(
scroll_speed,
currentMonitorWorkspaces,
hideUnoccupied,
);
self.on_scroll_up = throttledScrollUp;
self.on_scroll_down = throttledScrollDown;
},
);
},
},
};
};

View File

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

View File

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

View File

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