config: use config file
Config file at ~/.config/caelestia/shell.json
This commit is contained in:
parent
a6a6ef8a94
commit
cf55e26133
9 changed files with 291 additions and 214 deletions
5
app.tsx
5
app.tsx
|
|
@ -9,8 +9,9 @@ import Players from "@/services/players";
|
||||||
import type PopupWindow from "@/widgets/popupwindow";
|
import type PopupWindow from "@/widgets/popupwindow";
|
||||||
import { execAsync, GLib, monitorFile, readFileAsync, writeFileAsync } from "astal";
|
import { execAsync, GLib, monitorFile, readFileAsync, writeFileAsync } from "astal";
|
||||||
import { App } from "astal/gtk3";
|
import { App } from "astal/gtk3";
|
||||||
|
import { initConfig, updateConfig } from "config";
|
||||||
|
|
||||||
const loadStyleAsync = async () => {
|
export const loadStyleAsync = async () => {
|
||||||
let schemeColours;
|
let schemeColours;
|
||||||
if (GLib.file_test(`${STATE}/scheme/current.txt`, GLib.FileTest.EXISTS)) {
|
if (GLib.file_test(`${STATE}/scheme/current.txt`, GLib.FileTest.EXISTS)) {
|
||||||
const currentScheme = await readFileAsync(`${STATE}/scheme/current.txt`);
|
const currentScheme = await readFileAsync(`${STATE}/scheme/current.txt`);
|
||||||
|
|
@ -33,6 +34,7 @@ App.start({
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
loadStyleAsync().catch(console.error);
|
loadStyleAsync().catch(console.error);
|
||||||
monitorFile(`${STATE}/scheme/current.txt`, () => loadStyleAsync().catch(console.error));
|
monitorFile(`${STATE}/scheme/current.txt`, () => loadStyleAsync().catch(console.error));
|
||||||
|
initConfig();
|
||||||
|
|
||||||
<Launcher />;
|
<Launcher />;
|
||||||
<NotifPopups />;
|
<NotifPopups />;
|
||||||
|
|
@ -46,6 +48,7 @@ App.start({
|
||||||
requestHandler(request, res) {
|
requestHandler(request, res) {
|
||||||
if (request === "quit") App.quit();
|
if (request === "quit") App.quit();
|
||||||
else if (request === "reload-css") loadStyleAsync().catch(console.error);
|
else if (request === "reload-css") loadStyleAsync().catch(console.error);
|
||||||
|
else if (request === "reload-config") updateConfig();
|
||||||
else if (request.startsWith("show")) App.get_window(request.split(" ")[1])?.show();
|
else if (request.startsWith("show")) App.get_window(request.split(" ")[1])?.show();
|
||||||
else if (request === "toggle sideleft") {
|
else if (request === "toggle sideleft") {
|
||||||
const window = App.get_window("sideleft") as PopupWindow | null;
|
const window = App.get_window("sideleft") as PopupWindow | null;
|
||||||
|
|
|
||||||
224
config.ts
224
config.ts
|
|
@ -1,140 +1,166 @@
|
||||||
|
import { GLib, monitorFile, readFileAsync, Variable } from "astal";
|
||||||
import { Astal } from "astal/gtk3";
|
import { Astal } from "astal/gtk3";
|
||||||
|
import { loadStyleAsync } from "./app";
|
||||||
|
|
||||||
// Modules
|
const CONFIG = `${GLib.get_user_config_dir()}/caelestia/shell.json`;
|
||||||
export const bar = {
|
|
||||||
vertical: true,
|
const s = <T>(v: T): Variable<T> => Variable(v);
|
||||||
modules: {
|
|
||||||
osIcon: {
|
const warn = (e: Error) => console.warn(`Invalid config: ${e}`);
|
||||||
enabled: true,
|
|
||||||
},
|
const updateSection = (from: { [k: string]: any }, to: { [k: string]: any }, path: string) => {
|
||||||
activeWindow: {
|
for (const [k, v] of Object.entries(from)) {
|
||||||
enabled: true,
|
if (to.hasOwnProperty(k)) {
|
||||||
},
|
if (typeof v === "object" && v !== null && !Array.isArray(v)) updateSection(v, to[k], `${path}.${k}`);
|
||||||
mediaPlaying: {
|
else if (typeof v === typeof to[k].get()) to[k].set(v);
|
||||||
enabled: true,
|
else console.warn(`Invalid type for ${path}.${k}: ${typeof v} != ${typeof to[k].get()}`);
|
||||||
},
|
} else console.warn(`Unknown config key: ${path}.${k}`);
|
||||||
workspaces: {
|
}
|
||||||
enabled: true,
|
|
||||||
shown: 5,
|
|
||||||
},
|
|
||||||
tray: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
statusIcons: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
pkgUpdates: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
notifCount: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
battery: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
dateTime: {
|
|
||||||
enabled: true,
|
|
||||||
format: "%d/%m/%y %R",
|
|
||||||
detailedFormat: "%c",
|
|
||||||
},
|
|
||||||
power: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const launcher = {
|
export const updateConfig = async () => {
|
||||||
maxResults: 15, // Max shown results at one time (i.e. max height of the launcher)
|
const conf: { [k: string]: any } = JSON.parse(await readFileAsync(CONFIG));
|
||||||
|
for (const [k, v] of Object.entries(conf)) {
|
||||||
|
if (config.hasOwnProperty(k)) updateSection(v, config[k as keyof typeof config], k);
|
||||||
|
else console.warn(`Unknown config key: ${k}`);
|
||||||
|
}
|
||||||
|
loadStyleAsync().catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initConfig = () => {
|
||||||
|
monitorFile(CONFIG, () => updateConfig().catch(warn));
|
||||||
|
updateConfig().catch(warn);
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
// Modules
|
||||||
|
bar: {
|
||||||
|
vertical: s(true),
|
||||||
|
modules: {
|
||||||
|
osIcon: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
activeWindow: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
mediaPlaying: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
workspaces: {
|
||||||
|
enabled: s(true),
|
||||||
|
shown: s(5),
|
||||||
|
},
|
||||||
|
tray: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
statusIcons: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
pkgUpdates: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
notifCount: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
battery: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
dateTime: {
|
||||||
|
enabled: s(true),
|
||||||
|
format: s("%d/%m/%y %R"),
|
||||||
|
detailedFormat: s("%c"),
|
||||||
|
},
|
||||||
|
power: {
|
||||||
|
enabled: s(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
launcher: {
|
||||||
|
maxResults: s(15), // Max shown results at one time (i.e. max height of the launcher)
|
||||||
apps: {
|
apps: {
|
||||||
maxResults: 30, // Actual max results, -1 for infinite
|
maxResults: s(30), // Actual max results, -1 for infinite
|
||||||
pins: [
|
pins: s([
|
||||||
["firefox", "waterfox", "google-chrome", "chromium", "brave-browser", "vivaldi-stable", "vivaldi-snapshot"],
|
["zen", "firefox", "waterfox", "google-chrome", "chromium", "brave-browser"],
|
||||||
["foot", "alacritty", "kitty", "wezterm"],
|
["foot", "alacritty", "kitty", "wezterm"],
|
||||||
["thunar", "nemo", "nautilus"],
|
["thunar", "nemo", "nautilus"],
|
||||||
["codium", "code", "clion", "intellij-idea-ultimate-edition"],
|
["codium", "code", "clion", "intellij-idea-ultimate-edition"],
|
||||||
["spotify-adblock", "spotify", "audacious", "elisa"],
|
["spotify-adblock", "spotify", "audacious", "elisa"],
|
||||||
],
|
]),
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
maxResults: 40, // Actual max results, -1 for infinite
|
maxResults: s(40), // Actual max results, -1 for infinite
|
||||||
fdOpts: ["-a", "-t", "f"], // Options to pass to `fd`
|
fdOpts: s(["-a", "-t", "f"]), // Options to pass to `fd`
|
||||||
},
|
},
|
||||||
math: {
|
math: {
|
||||||
maxResults: 40, // Actual max results, -1 for infinite
|
maxResults: s(40), // Actual max results, -1 for infinite
|
||||||
},
|
},
|
||||||
windows: {
|
windows: {
|
||||||
maxResults: -1, // Actual max results, -1 for infinite
|
maxResults: s(-1), // Actual max results, -1 for infinite
|
||||||
weights: {
|
weights: {
|
||||||
// Weights for fuzzy sort
|
// Weights for fuzzy sort
|
||||||
title: 1,
|
title: s(1),
|
||||||
class: 1,
|
class: s(1),
|
||||||
initialTitle: 0.5,
|
initialTitle: s(0.5),
|
||||||
initialClass: 0.5,
|
initialClass: s(0.5),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
todo: {
|
todo: {
|
||||||
notify: true,
|
notify: s(true),
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
notifpopups: {
|
||||||
export const notifpopups = {
|
maxPopups: s(-1),
|
||||||
maxPopups: -1,
|
expire: s(false),
|
||||||
expire: false,
|
agoTime: s(true), // Whether to show time in ago format, e.g. 10 mins ago, or raw time, e.g. 10:42
|
||||||
agoTime: true, // Whether to show time in ago format, e.g. 10 mins ago, or raw time, e.g. 10:42
|
},
|
||||||
};
|
osds: {
|
||||||
|
|
||||||
export const osds = {
|
|
||||||
volume: {
|
volume: {
|
||||||
position: Astal.WindowAnchor.RIGHT,
|
position: s(Astal.WindowAnchor.RIGHT),
|
||||||
margin: 20,
|
margin: s(20),
|
||||||
hideDelay: 1500,
|
hideDelay: s(1500),
|
||||||
showValue: true,
|
showValue: s(true),
|
||||||
},
|
},
|
||||||
brightness: {
|
brightness: {
|
||||||
position: Astal.WindowAnchor.LEFT,
|
position: s(Astal.WindowAnchor.LEFT),
|
||||||
margin: 20,
|
margin: s(20),
|
||||||
hideDelay: 1500,
|
hideDelay: s(1500),
|
||||||
showValue: true,
|
showValue: s(true),
|
||||||
},
|
},
|
||||||
lock: {
|
lock: {
|
||||||
spacing: 5,
|
spacing: s(5),
|
||||||
caps: {
|
caps: {
|
||||||
hideDelay: 1000,
|
hideDelay: s(1000),
|
||||||
},
|
},
|
||||||
num: {
|
num: {
|
||||||
hideDelay: 1000,
|
hideDelay: s(1000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
// Services
|
||||||
// Services
|
math: {
|
||||||
export const math = {
|
|
||||||
maxHistory: 100,
|
maxHistory: 100,
|
||||||
};
|
},
|
||||||
|
updates: {
|
||||||
export const updates = {
|
|
||||||
interval: 900000,
|
interval: 900000,
|
||||||
};
|
},
|
||||||
|
weather: {
|
||||||
export const weather = {
|
|
||||||
interval: 600000,
|
interval: 600000,
|
||||||
key: "assets/weather-api-key.txt", // Path to file containing api key relative to the base directory. To get a key, visit https://weatherapi.com/
|
key: "assets/weather-api-key.txt", // Path to file containing api key relative to the base directory. To get a key, visit https://weatherapi.com/
|
||||||
location: "", // Location as a string or empty to autodetect
|
location: "", // Location as a string or empty to autodetect
|
||||||
imperial: false,
|
imperial: false,
|
||||||
};
|
},
|
||||||
|
cpu: {
|
||||||
export const cpu = {
|
|
||||||
interval: 2000,
|
interval: 2000,
|
||||||
};
|
},
|
||||||
|
gpu: {
|
||||||
export const gpu = {
|
|
||||||
interval: 2000,
|
interval: 2000,
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
interval: 5000,
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
interval: 5000,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const memory = {
|
export const { bar, launcher, notifpopups, osds, math, updates, weather, cpu, gpu, memory, storage } = config;
|
||||||
interval: 5000,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const storage = {
|
|
||||||
interval: 5000,
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type { AstalWidget } from "@/utils/types";
|
||||||
import { setupCustomTooltip } from "@/utils/widgets";
|
import { setupCustomTooltip } from "@/utils/widgets";
|
||||||
import type PopupWindow from "@/widgets/popupwindow";
|
import type PopupWindow from "@/widgets/popupwindow";
|
||||||
import { execAsync, Variable } from "astal";
|
import { execAsync, Variable } from "astal";
|
||||||
import { bind, kebabify } from "astal/binding";
|
import Binding, { bind, kebabify } from "astal/binding";
|
||||||
import { App, Astal, Gtk } from "astal/gtk3";
|
import { App, Astal, Gtk } from "astal/gtk3";
|
||||||
import { bar as config } from "config";
|
import { bar as config } from "config";
|
||||||
import AstalBattery from "gi://AstalBattery";
|
import AstalBattery from "gi://AstalBattery";
|
||||||
|
|
@ -17,7 +17,7 @@ import AstalHyprland from "gi://AstalHyprland";
|
||||||
import AstalNetwork from "gi://AstalNetwork";
|
import AstalNetwork from "gi://AstalNetwork";
|
||||||
import AstalNotifd from "gi://AstalNotifd";
|
import AstalNotifd from "gi://AstalNotifd";
|
||||||
import AstalTray from "gi://AstalTray";
|
import AstalTray from "gi://AstalTray";
|
||||||
import AstalWp01 from "gi://AstalWp";
|
import AstalWp from "gi://AstalWp";
|
||||||
|
|
||||||
const hyprland = AstalHyprland.get_default();
|
const hyprland = AstalHyprland.get_default();
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ const OSIcon = () => (
|
||||||
|
|
||||||
const ActiveWindow = () => (
|
const ActiveWindow = () => (
|
||||||
<box
|
<box
|
||||||
vertical={config.vertical}
|
vertical={bind(config.vertical)}
|
||||||
className="module active-window"
|
className="module active-window"
|
||||||
setup={self => {
|
setup={self => {
|
||||||
const title = Variable("");
|
const title = Variable("");
|
||||||
|
|
@ -109,14 +109,15 @@ const ActiveWindow = () => (
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
angle={config.vertical ? 270 : 0}
|
angle={bind(config.vertical).as(v => (v ? 270 : 0))}
|
||||||
setup={self =>
|
setup={self => {
|
||||||
hookFocusedClientProp(
|
const update = () =>
|
||||||
self,
|
(self.label = hyprland.focusedClient?.title
|
||||||
"title",
|
? ellipsize(hyprland.focusedClient.title, config.vertical.get() ? 25 : 40)
|
||||||
c => (self.label = c?.title ? ellipsize(c.title, config.vertical ? 25 : 40) : "Desktop")
|
: "Desktop");
|
||||||
)
|
hookFocusedClientProp(self, "title", update);
|
||||||
}
|
self.hook(config.vertical, update);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
|
@ -139,7 +140,7 @@ const MediaPlaying = () => {
|
||||||
setupCustomTooltip(self, bind(label));
|
setupCustomTooltip(self, bind(label));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<box vertical={config.vertical} className="module media-playing">
|
<box vertical={bind(config.vertical)} className="module media-playing">
|
||||||
<icon
|
<icon
|
||||||
setup={self =>
|
setup={self =>
|
||||||
players.hookLastPlayer(self, "notify::identity", () => {
|
players.hookLastPlayer(self, "notify::identity", () => {
|
||||||
|
|
@ -155,12 +156,14 @@ const MediaPlaying = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
angle={config.vertical ? 270 : 0}
|
angle={bind(config.vertical).as(v => (v ? 270 : 0))}
|
||||||
setup={self =>
|
setup={self => {
|
||||||
players.hookLastPlayer(self, ["notify::title", "notify::artist"], () => {
|
// TODO: scroll text when playing or hover
|
||||||
self.label = ellipsize(getLabel("No media"), config.vertical ? 25 : 40); // TODO: scroll text when playing or hover
|
const update = () =>
|
||||||
})
|
(self.label = ellipsize(getLabel("No media"), config.vertical.get() ? 25 : 40));
|
||||||
}
|
players.hookLastPlayer(self, ["notify::title", "notify::artist"], update);
|
||||||
|
self.hook(config.vertical, update);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -169,8 +172,8 @@ const MediaPlaying = () => {
|
||||||
|
|
||||||
const Workspace = ({ idx }: { idx: number }) => {
|
const Workspace = ({ idx }: { idx: number }) => {
|
||||||
let wsId = hyprland.focusedWorkspace
|
let wsId = hyprland.focusedWorkspace
|
||||||
? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) *
|
? Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) *
|
||||||
config.modules.workspaces.shown +
|
config.modules.workspaces.shown.get() +
|
||||||
idx
|
idx
|
||||||
: idx;
|
: idx;
|
||||||
return (
|
return (
|
||||||
|
|
@ -184,16 +187,18 @@ const Workspace = ({ idx }: { idx: number }) => {
|
||||||
"occupied",
|
"occupied",
|
||||||
hyprland.clients.some(c => c.workspace?.id === wsId)
|
hyprland.clients.some(c => c.workspace?.id === wsId)
|
||||||
);
|
);
|
||||||
|
const updateWs = () => {
|
||||||
self.hook(hyprland, "notify::focused-workspace", () => {
|
|
||||||
if (!hyprland.focusedWorkspace) return;
|
if (!hyprland.focusedWorkspace) return;
|
||||||
wsId =
|
wsId =
|
||||||
Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown) *
|
Math.floor((hyprland.focusedWorkspace.id - 1) / config.modules.workspaces.shown.get()) *
|
||||||
config.modules.workspaces.shown +
|
config.modules.workspaces.shown.get() +
|
||||||
idx;
|
idx;
|
||||||
self.toggleClassName("focused", hyprland.focusedWorkspace.id === wsId);
|
self.toggleClassName("focused", hyprland.focusedWorkspace.id === wsId);
|
||||||
update();
|
update();
|
||||||
});
|
};
|
||||||
|
|
||||||
|
self.hook(config.modules.workspaces.shown, updateWs);
|
||||||
|
self.hook(hyprland, "notify::focused-workspace", () => updateWs);
|
||||||
self.hook(hyprland, "client-added", update);
|
self.hook(hyprland, "client-added", update);
|
||||||
self.hook(hyprland, "client-moved", update);
|
self.hook(hyprland, "client-moved", update);
|
||||||
self.hook(hyprland, "client-removed", update);
|
self.hook(hyprland, "client-removed", update);
|
||||||
|
|
@ -214,10 +219,10 @@ const Workspaces = () => (
|
||||||
hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1);
|
hyprland.dispatch("workspace", (event.delta_y < 0 ? "-" : "+") + 1);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<box vertical={config.vertical} className="module workspaces">
|
<box vertical={bind(config.vertical)} className="module workspaces">
|
||||||
{Array.from({ length: config.modules.workspaces.shown }).map((_, idx) => (
|
{bind(config.modules.workspaces.shown).as(
|
||||||
<Workspace idx={idx + 1} /> // Start from 1
|
n => Array.from({ length: n }).map((_, idx) => <Workspace idx={idx + 1} />) // Start from 1
|
||||||
))}
|
)}
|
||||||
</box>
|
</box>
|
||||||
</eventbox>
|
</eventbox>
|
||||||
);
|
);
|
||||||
|
|
@ -226,7 +231,7 @@ const TrayItem = (item: AstalTray.TrayItem) => (
|
||||||
<menubutton
|
<menubutton
|
||||||
onButtonPressEvent={(_, event) => event.get_button()[1] === Astal.MouseButton.SECONDARY && item.activate(0, 0)}
|
onButtonPressEvent={(_, event) => event.get_button()[1] === Astal.MouseButton.SECONDARY && item.activate(0, 0)}
|
||||||
usePopover={false}
|
usePopover={false}
|
||||||
direction={config.vertical ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN}
|
direction={bind(config.vertical).as(v => (v ? Gtk.ArrowType.RIGHT : Gtk.ArrowType.DOWN))}
|
||||||
menuModel={bind(item, "menuModel")}
|
menuModel={bind(item, "menuModel")}
|
||||||
actionGroup={bind(item, "actionGroup").as(a => ["dbusmenu", a])}
|
actionGroup={bind(item, "actionGroup").as(a => ["dbusmenu", a])}
|
||||||
setup={self => setupCustomTooltip(self, bind(item, "tooltipMarkup"))}
|
setup={self => setupCustomTooltip(self, bind(item, "tooltipMarkup"))}
|
||||||
|
|
@ -237,7 +242,7 @@ const TrayItem = (item: AstalTray.TrayItem) => (
|
||||||
|
|
||||||
const Tray = () => (
|
const Tray = () => (
|
||||||
<box
|
<box
|
||||||
vertical={config.vertical}
|
vertical={bind(config.vertical)}
|
||||||
className="module tray"
|
className="module tray"
|
||||||
visible={bind(AstalTray.get_default(), "items").as(i => i.length > 0)}
|
visible={bind(AstalTray.get_default(), "items").as(i => i.length > 0)}
|
||||||
>
|
>
|
||||||
|
|
@ -370,7 +375,7 @@ const BluetoothDevice = (device: AstalBluetooth.Device) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const Bluetooth = () => (
|
const Bluetooth = () => (
|
||||||
<box vertical={config.vertical} className="bluetooth">
|
<box vertical={bind(config.vertical)} className="bluetooth">
|
||||||
<button
|
<button
|
||||||
onClick={(self, event) => {
|
onClick={(self, event) => {
|
||||||
if (event.button === Astal.MouseButton.PRIMARY) togglePopup(self, event, "bluetooth-devices");
|
if (event.button === Astal.MouseButton.PRIMARY) togglePopup(self, event, "bluetooth-devices");
|
||||||
|
|
@ -416,7 +421,7 @@ const Bluetooth = () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const StatusIcons = () => (
|
const StatusIcons = () => (
|
||||||
<box vertical={config.vertical} className="module status-icons">
|
<box vertical={bind(config.vertical)} className="module status-icons">
|
||||||
<Network />
|
<Network />
|
||||||
<Bluetooth />
|
<Bluetooth />
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -432,7 +437,7 @@ const PkgUpdates = () => (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<box vertical={config.vertical} className="module pkg-updates">
|
<box vertical={bind(config.vertical)} className="module pkg-updates">
|
||||||
<label className="icon" label="download" />
|
<label className="icon" label="download" />
|
||||||
<label label={bind(Updates.get_default(), "numUpdates").as(String)} />
|
<label label={bind(Updates.get_default(), "numUpdates").as(String)} />
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -453,7 +458,7 @@ const NotifCount = () => (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<box vertical={config.vertical} className="module notif-count">
|
<box vertical={bind(config.vertical)} className="module notif-count">
|
||||||
<label className="icon" label="info" />
|
<label className="icon" label="info" />
|
||||||
<label label={bind(AstalNotifd.get_default(), "notifications").as(n => String(n.length))} />
|
<label label={bind(AstalNotifd.get_default(), "notifications").as(n => String(n.length))} />
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -472,7 +477,7 @@ const Battery = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box
|
<box
|
||||||
vertical={config.vertical}
|
vertical={bind(config.vertical)}
|
||||||
className={bind(className)}
|
className={bind(className)}
|
||||||
setup={self => setupCustomTooltip(self, bind(tooltip))}
|
setup={self => setupCustomTooltip(self, bind(tooltip))}
|
||||||
onDestroy={() => {
|
onDestroy={() => {
|
||||||
|
|
@ -489,11 +494,20 @@ const Battery = () => {
|
||||||
const DateTime = () => (
|
const DateTime = () => (
|
||||||
<button
|
<button
|
||||||
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")}
|
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")}
|
||||||
setup={self => setupCustomTooltip(self, bindCurrentTime(config.modules.dateTime.detailedFormat))}
|
setup={self =>
|
||||||
|
setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<box className="module date-time">
|
<box className="module date-time">
|
||||||
<label className="icon" label="calendar_month" />
|
<label className="icon" label="calendar_month" />
|
||||||
<label label={bindCurrentTime(config.modules.dateTime.format)} />
|
<label
|
||||||
|
setup={self =>
|
||||||
|
self.hook(
|
||||||
|
bindCurrentTime(bind(config.modules.dateTime.format), undefined, self),
|
||||||
|
(_, t) => (self.label = t)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</box>
|
</box>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
@ -501,7 +515,9 @@ const DateTime = () => (
|
||||||
const DateTimeVertical = () => (
|
const DateTimeVertical = () => (
|
||||||
<button
|
<button
|
||||||
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")}
|
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideright")}
|
||||||
setup={self => setupCustomTooltip(self, bindCurrentTime(config.modules.dateTime.detailedFormat))}
|
setup={self =>
|
||||||
|
setupCustomTooltip(self, bindCurrentTime(bind(config.modules.dateTime.detailedFormat), undefined, self))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<box vertical className="module date-time">
|
<box vertical className="module date-time">
|
||||||
<label className="icon" label="calendar_month" />
|
<label className="icon" label="calendar_month" />
|
||||||
|
|
@ -519,22 +535,34 @@ const Power = () => (
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const Dummy = () => <box visible={false} />; // Invisible box cause otherwise shows as text
|
||||||
|
|
||||||
|
const bindWidget = (module: keyof typeof config.modules, Widget: () => JSX.Element) =>
|
||||||
|
bind(config.modules[module].enabled).as(e => (e ? <Widget /> : <Dummy />));
|
||||||
|
|
||||||
|
const bindCompositeWidget = (module: keyof typeof config.modules, binding: Binding<JSX.Element>) =>
|
||||||
|
bind(Variable.derive([config.modules[module].enabled, binding], (e, w) => (e ? w : <Dummy />)));
|
||||||
|
|
||||||
export default ({ monitor }: { monitor: Monitor }) => (
|
export default ({ monitor }: { monitor: Monitor }) => (
|
||||||
<window
|
<window
|
||||||
namespace="caelestia-bar"
|
namespace="caelestia-bar"
|
||||||
monitor={monitor.id}
|
monitor={monitor.id}
|
||||||
anchor={
|
anchor={bind(config.vertical).as(
|
||||||
|
v =>
|
||||||
Astal.WindowAnchor.TOP |
|
Astal.WindowAnchor.TOP |
|
||||||
Astal.WindowAnchor.LEFT |
|
Astal.WindowAnchor.LEFT |
|
||||||
(config.vertical ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT)
|
(v ? Astal.WindowAnchor.BOTTOM : Astal.WindowAnchor.RIGHT)
|
||||||
}
|
)}
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
>
|
>
|
||||||
<centerbox vertical={config.vertical} className={`bar ${config.vertical ? "vertical" : " horizontal"}`}>
|
<centerbox
|
||||||
<box vertical={config.vertical}>
|
vertical={bind(config.vertical)}
|
||||||
{config.modules.osIcon.enabled && <OSIcon />}
|
className={bind(config.vertical).as(v => `bar ${v ? "vertical" : " horizontal"}`)}
|
||||||
{config.modules.activeWindow.enabled && <ActiveWindow />}
|
>
|
||||||
{config.modules.mediaPlaying.enabled && <MediaPlaying />}
|
<box vertical={bind(config.vertical)}>
|
||||||
|
{bindWidget("osIcon", OSIcon)}
|
||||||
|
{bindWidget("activeWindow", ActiveWindow)}
|
||||||
|
{bindWidget("mediaPlaying", MediaPlaying)}
|
||||||
<button
|
<button
|
||||||
expand
|
expand
|
||||||
onScroll={(_, event) =>
|
onScroll={(_, event) =>
|
||||||
|
|
@ -542,25 +570,31 @@ export default ({ monitor }: { monitor: Monitor }) => (
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
{config.modules.workspaces.enabled && <Workspaces />}
|
{bindWidget("workspaces", Workspaces)}
|
||||||
<box vertical={config.vertical}>
|
<box vertical={bind(config.vertical)}>
|
||||||
<button
|
<button
|
||||||
expand
|
expand
|
||||||
onScroll={(_, event) => {
|
onScroll={(_, event) => {
|
||||||
const speaker = AstalWp01.get_default()?.audio.defaultSpeaker;
|
const speaker = AstalWp.get_default()?.audio.defaultSpeaker;
|
||||||
if (!speaker) return;
|
if (!speaker) return;
|
||||||
speaker.mute = false;
|
speaker.mute = false;
|
||||||
if (event.delta_y > 0) speaker.volume -= 0.1;
|
if (event.delta_y > 0) speaker.volume -= 0.1;
|
||||||
else speaker.volume += 0.1;
|
else speaker.volume += 0.1;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{config.modules.tray.enabled && <Tray />}
|
{bindWidget("tray", Tray)}
|
||||||
{config.modules.statusIcons.enabled && <StatusIcons />}
|
{bindWidget("statusIcons", StatusIcons)}
|
||||||
{config.modules.pkgUpdates.enabled && <PkgUpdates />}
|
{bindWidget("pkgUpdates", PkgUpdates)}
|
||||||
{config.modules.notifCount.enabled && <NotifCount />}
|
{bindWidget("notifCount", NotifCount)}
|
||||||
{config.modules.battery.enabled && AstalBattery.get_default().isBattery && <Battery />}
|
{bindCompositeWidget(
|
||||||
{config.modules.dateTime.enabled && (config.vertical ? <DateTimeVertical /> : <DateTime />)}
|
"battery",
|
||||||
{config.modules.power.enabled && <Power />}
|
bind(AstalBattery.get_default(), "isBattery").as(b => (b ? <Battery /> : <Dummy />))
|
||||||
|
)}
|
||||||
|
{bindCompositeWidget(
|
||||||
|
"dateTime",
|
||||||
|
bind(config.vertical).as(v => (v ? <DateTimeVertical /> : <DateTime />))
|
||||||
|
)}
|
||||||
|
{bindWidget("power", Power)}
|
||||||
</box>
|
</box>
|
||||||
</centerbox>
|
</centerbox>
|
||||||
</window>
|
</window>
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ const getEmptyTextFromMode = (mode: Mode) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const limitLength = <T,>(arr: T[], cfg: { maxResults: number }) =>
|
const limitLength = <T,>(arr: T[], cfg: { maxResults: Variable<number> }) =>
|
||||||
cfg.maxResults > 0 && arr.length > cfg.maxResults ? arr.slice(0, cfg.maxResults) : arr;
|
cfg.maxResults.get() > 0 && arr.length > cfg.maxResults.get() ? arr.slice(0, cfg.maxResults.get()) : arr;
|
||||||
|
|
||||||
const close = (self: JSX.Element) => {
|
const close = (self: JSX.Element) => {
|
||||||
const toplevel = self.get_toplevel();
|
const toplevel = self.get_toplevel();
|
||||||
|
|
@ -117,7 +117,7 @@ const PinnedApp = (names: string[]) => {
|
||||||
return widget;
|
return widget;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PinnedApps = () => <box homogeneous>{config.apps.pins.map(PinnedApp)}</box>;
|
const PinnedApps = () => <box homogeneous>{bind(config.apps.pins).as(p => p.map(PinnedApp))}</box>;
|
||||||
|
|
||||||
const SearchEntry = ({ entry }: { entry: Widget.Entry }) => (
|
const SearchEntry = ({ entry }: { entry: Widget.Entry }) => (
|
||||||
<stack
|
<stack
|
||||||
|
|
@ -560,7 +560,7 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
|
|
||||||
// Create todo, notify and close
|
// Create todo, notify and close
|
||||||
execAsync(`tod t q -c ${args.join(" ")}`).catch(console.error);
|
execAsync(`tod t q -c ${args.join(" ")}`).catch(console.error);
|
||||||
if (config.todo.notify)
|
if (config.todo.notify.get())
|
||||||
notify({
|
notify({
|
||||||
summary: "Todo created",
|
summary: "Todo created",
|
||||||
body: `Created todo with content: ${args.join(" ")}`,
|
body: `Created todo with content: ${args.join(" ")}`,
|
||||||
|
|
@ -616,7 +616,7 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileSearch = () =>
|
const fileSearch = () =>
|
||||||
execAsync(["fd", ...config.files.fdOpts, entry.text, HOME])
|
execAsync(["fd", ...config.files.fdOpts.get(), entry.text, HOME])
|
||||||
.then(out => {
|
.then(out => {
|
||||||
const paths = out.split("\n").filter(path => path);
|
const paths = out.split("\n").filter(path => path);
|
||||||
self.foreach(ch => ch.destroy());
|
self.foreach(ch => ch.destroy());
|
||||||
|
|
@ -638,13 +638,16 @@ const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> })
|
||||||
if (entry.text) {
|
if (entry.text) {
|
||||||
const clients = fuzzysort.go(entry.text, unsortedClients, {
|
const clients = fuzzysort.go(entry.text, unsortedClients, {
|
||||||
all: true,
|
all: true,
|
||||||
limit: config.windows.maxResults < 0 ? undefined : config.windows.maxResults,
|
limit:
|
||||||
|
config.windows.maxResults.get() < 0
|
||||||
|
? undefined
|
||||||
|
: config.windows.maxResults.get(),
|
||||||
keys: ["title", "class", "initialTitle", "initialClass"],
|
keys: ["title", "class", "initialTitle", "initialClass"],
|
||||||
scoreFn: r =>
|
scoreFn: r =>
|
||||||
r[0].score * config.windows.weights.title +
|
r[0].score * config.windows.weights.title.get() +
|
||||||
r[1].score * config.windows.weights.class +
|
r[1].score * config.windows.weights.class.get() +
|
||||||
r[2].score * config.windows.weights.initialTitle +
|
r[2].score * config.windows.weights.initialTitle.get() +
|
||||||
r[3].score * config.windows.weights.initialClass,
|
r[3].score * config.windows.weights.initialClass.get(),
|
||||||
});
|
});
|
||||||
self.foreach(ch => ch.destroy());
|
self.foreach(ch => ch.destroy());
|
||||||
for (const { obj } of clients)
|
for (const { obj } of clients)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
// Limit number of popups
|
// Limit number of popups
|
||||||
if (config.maxPopups > 0 && self.children.length > config.maxPopups)
|
if (config.maxPopups.get() > 0 && self.children.length > config.maxPopups.get())
|
||||||
map.values().next().value?.destroyWithAnims();
|
map.values().next().value?.destroyWithAnims();
|
||||||
});
|
});
|
||||||
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Monitors, { type Monitor } from "@/services/monitors";
|
import Monitors, { type Monitor } from "@/services/monitors";
|
||||||
import PopupWindow from "@/widgets/popupwindow";
|
import PopupWindow from "@/widgets/popupwindow";
|
||||||
import { execAsync, register, timeout, Variable, type Time } from "astal";
|
import { bind, execAsync, register, timeout, Variable, type Time } from "astal";
|
||||||
import { App, Astal, Gtk, Widget } from "astal/gtk3";
|
import { App, Astal, Gtk, Widget } from "astal/gtk3";
|
||||||
import cairo from "cairo";
|
import cairo from "cairo";
|
||||||
import { osds as config } from "config";
|
import { osds as config } from "config";
|
||||||
|
|
@ -52,13 +52,13 @@ const SliderOsd = ({
|
||||||
name={type}
|
name={type}
|
||||||
monitor={monitor?.id}
|
monitor={monitor?.id}
|
||||||
keymode={Astal.Keymode.NONE}
|
keymode={Astal.Keymode.NONE}
|
||||||
anchor={config[type].position}
|
anchor={bind(config[type].position)}
|
||||||
margin={config[type].margin}
|
margin={bind(config[type].margin)}
|
||||||
setup={self => {
|
setup={self => {
|
||||||
let time: Time | null = null;
|
let time: Time | null = null;
|
||||||
const hideAfterTimeout = () => {
|
const hideAfterTimeout = () => {
|
||||||
time?.cancel();
|
time?.cancel();
|
||||||
time = timeout(config[type].hideDelay, () => self.hide());
|
time = timeout(config[type].hideDelay.get(), () => self.hide());
|
||||||
};
|
};
|
||||||
self.connect("show", hideAfterTimeout);
|
self.connect("show", hideAfterTimeout);
|
||||||
windowSetup(self, () => {
|
windowSetup(self, () => {
|
||||||
|
|
@ -74,8 +74,8 @@ const SliderOsd = ({
|
||||||
setup={self => {
|
setup={self => {
|
||||||
const halfPi = Math.PI / 2;
|
const halfPi = Math.PI / 2;
|
||||||
const vertical =
|
const vertical =
|
||||||
config[type].position === Astal.WindowAnchor.LEFT ||
|
config[type].position.get() === Astal.WindowAnchor.LEFT ||
|
||||||
config[type].position === Astal.WindowAnchor.RIGHT;
|
config[type].position.get() === Astal.WindowAnchor.RIGHT;
|
||||||
|
|
||||||
const icon = Variable("");
|
const icon = Variable("");
|
||||||
drawAreaSetup(self, icon);
|
drawAreaSetup(self, icon);
|
||||||
|
|
@ -134,7 +134,7 @@ const SliderOsd = ({
|
||||||
// Progress number, at top/right
|
// Progress number, at top/right
|
||||||
let nw = 0;
|
let nw = 0;
|
||||||
let nh = 0;
|
let nh = 0;
|
||||||
if (config[type].showValue) {
|
if (config[type].showValue.get()) {
|
||||||
const numLayout = parent.create_pango_layout(String(Math.round(progressValue * 100)));
|
const numLayout = parent.create_pango_layout(String(Math.round(progressValue * 100)));
|
||||||
[nw, nh] = numLayout.get_pixel_size();
|
[nw, nh] = numLayout.get_pixel_size();
|
||||||
let diff;
|
let diff;
|
||||||
|
|
@ -295,7 +295,7 @@ class LockOsd extends Widget.Window {
|
||||||
const child = this.get_child();
|
const child = this.get_child();
|
||||||
if (!child) return;
|
if (!child) return;
|
||||||
this[right ? "marginLeft" : "marginRight"] = window.visible
|
this[right ? "marginLeft" : "marginRight"] = window.visible
|
||||||
? child.get_preferred_width()[1] + config.lock.spacing
|
? child.get_preferred_width()[1] + config.lock.spacing.get()
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -311,7 +311,7 @@ class LockOsd extends Widget.Window {
|
||||||
super.show();
|
super.show();
|
||||||
this.#update();
|
this.#update();
|
||||||
this.#timeout?.cancel();
|
this.#timeout?.cancel();
|
||||||
this.#timeout = timeout(config.lock[this.lockType].hideDelay, () => this.hide());
|
this.#timeout = timeout(config.lock[this.lockType].hideDelay.get(), () => this.hide());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { bind, execAsync, GLib, Variable, type Gio } from "astal";
|
import { bind, execAsync, GLib, Variable, type Binding, type Gio } from "astal";
|
||||||
import type AstalApps from "gi://AstalApps";
|
import type AstalApps from "gi://AstalApps";
|
||||||
import { osIcons } from "./icons";
|
import { osIcons } from "./icons";
|
||||||
|
|
||||||
|
|
@ -63,5 +63,15 @@ export const osIcon = (() => {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export const currentTime = Variable(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local());
|
export const currentTime = Variable(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local());
|
||||||
export const bindCurrentTime = (format: string, fallback?: (time: GLib.DateTime) => string) =>
|
export const bindCurrentTime = (
|
||||||
bind(currentTime).as(c => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString());
|
format: Binding<string> | string,
|
||||||
|
fallback?: (time: GLib.DateTime) => string,
|
||||||
|
self?: JSX.Element
|
||||||
|
) => {
|
||||||
|
const fmt = (c: GLib.DateTime, format: string) => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString();
|
||||||
|
if (typeof format === "string") return bind(currentTime).as(c => fmt(c, format));
|
||||||
|
if (!self) throw new Error("bindCurrentTime: self is required when format is a Binding");
|
||||||
|
const time = Variable.derive([currentTime, format], (c, f) => fmt(c, f));
|
||||||
|
self?.connect("destroy", () => time.drop());
|
||||||
|
return bind(time);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const getTime = (time: number) => {
|
||||||
const now = GLib.DateTime.new_now_local();
|
const now = GLib.DateTime.new_now_local();
|
||||||
const todayDay = now.get_day_of_year();
|
const todayDay = now.get_day_of_year();
|
||||||
|
|
||||||
if (config.agoTime) {
|
if (config.agoTime.get()) {
|
||||||
const diff = now.difference(messageTime) / 1e6;
|
const diff = now.difference(messageTime) / 1e6;
|
||||||
if (diff < 60) return "Now";
|
if (diff < 60) return "Now";
|
||||||
if (diff < 3600) {
|
if (diff < 3600) {
|
||||||
|
|
@ -75,6 +75,7 @@ export default class Notification extends Widget.Box {
|
||||||
super({ className: "notification" });
|
super({ className: "notification" });
|
||||||
|
|
||||||
const time = Variable(getTime(notification.time)).poll(60000, () => getTime(notification.time));
|
const time = Variable(getTime(notification.time)).poll(60000, () => getTime(notification.time));
|
||||||
|
this.hook(config.agoTime, () => time.set(getTime(notification.time)));
|
||||||
|
|
||||||
this.#revealer = (
|
this.#revealer = (
|
||||||
<revealer
|
<revealer
|
||||||
|
|
@ -138,7 +139,7 @@ export default class Notification extends Widget.Box {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close popup after timeout if transient or expire enabled in config
|
// Close popup after timeout if transient or expire enabled in config
|
||||||
if (popup && (config.expire || notification.transient))
|
if (popup && (config.expire.get() || notification.transient))
|
||||||
timeout(
|
timeout(
|
||||||
notification.expireTimeout > 0
|
notification.expireTimeout > 0
|
||||||
? notification.expireTimeout
|
? notification.expireTimeout
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export default class PopupWindow extends Widget.Window {
|
||||||
|
|
||||||
let marginLeft = 0;
|
let marginLeft = 0;
|
||||||
let marginTop = 0;
|
let marginTop = 0;
|
||||||
if (bar.vertical) {
|
if (bar.vertical.get()) {
|
||||||
marginLeft = cx + (width - x);
|
marginLeft = cx + (width - x);
|
||||||
marginTop = cy + ((height - pHeight) / 2 - y);
|
marginTop = cy + ((height - pHeight) / 2 - y);
|
||||||
if (marginTop < 0) marginTop = 0;
|
if (marginTop < 0) marginTop = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue