Refonte complete du rice. Palette 100% originale (Mitsuri Kanroji inspired), zero emprunt Dracula/Catppuccin. 50 fichiers, 3200+ lignes. Palette v2: - palette.sh source de verite unique (dark + light) - 5 accents (magenta, lilac, mitsuri, lavande, champagne) - 4 semantiques derivees, 4 niveaux texte, 6 fonds - Gradient signature: magenta → lilac → lavande → mitsuri - Variante Light: fonds lavande, accents assombris WCAG Terminal: - kitty (remplace COSMIC Term comme principal) - Maple Mono NF (cursive italics, ligatures) - Cursor trail magenta, splits/layouts tiling, undercurl - Vi-mode zsh avec cursor shape adaptatif Shell: - starship 3 lignes (palette nommee, brain_name, battery, sudo) - zshrc v2 (nouveaux outils, fzf pimp, shell functions, vi-mode) - Commandes custom: proj, glog, fkill, colors, hotkeys, weather, y Desktop: - AGS config (bar 3-pills, OSD gradient, launcher, notifications) - COSMIC Dark + Light v2 (7 fichiers RON chacun) - COSMIC Term v2 (color schemes dark/light, Maple Mono NF) - GTK3/GTK4 dark + light css - Vivaldi theme v2 Outils: - +kitty +dust +procs +tokei +sd +hyperfine +gping +Maple Mono NF - Propagation palette sur: bat, btop, cava, yazi, lazygit, rofi, delta, fastfetch, atuin, ls-colors, vivaldi - Claude Code statusline brain-aware Docs: - README v2 complet (palette, structure, raccourcis, commandes) - help.md v2 (reference exhaustive)
229 lines
9.1 KiB
JavaScript
229 lines
9.1 KiB
JavaScript
// ── violet-chaton v2 — Bar widget ───────────────────────────────────────────
|
|
// 3 pills glassmorphism — modulaire selon compositor
|
|
|
|
const audio = await Service.import("audio");
|
|
const battery = await Service.import("battery");
|
|
const network = await Service.import("network");
|
|
const systemtray = await Service.import("systemtray");
|
|
const mpris = await Service.import("mpris");
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
|
|
const Separator = () => Widget.Label({ className: "separator", label: "│" });
|
|
|
|
const Clock = () => Widget.Label({
|
|
className: "clock",
|
|
connections: [[1000, (self) => {
|
|
self.label = Utils.exec("date +%H:%M");
|
|
}]],
|
|
});
|
|
|
|
const DateWidget = () => Widget.Label({
|
|
className: "date",
|
|
connections: [[60000, (self) => {
|
|
self.label = Utils.exec("date '+%a %d %b'");
|
|
}]],
|
|
});
|
|
|
|
// ── System ──────────────────────────────────────────────────────────────────
|
|
|
|
const CPU = () => Widget.Label({
|
|
className: "cpu",
|
|
connections: [[2000, (self) => {
|
|
const usage = Math.round(
|
|
Number(Utils.exec(`bash -c "top -bn1 | awk '/^%Cpu/ {print 100-$8}'"`)
|
|
));
|
|
self.label = ` ${usage}%`;
|
|
self.toggleClassName("warning", usage > 70);
|
|
self.toggleClassName("critical", usage > 90);
|
|
}]],
|
|
});
|
|
|
|
const RAM = () => Widget.Label({
|
|
className: "ram",
|
|
connections: [[2000, (self) => {
|
|
const used = Utils.exec(`bash -c "free -m | awk '/^Mem:/ {printf \"%.1f\", $3/1024}'"`)
|
|
const total = Utils.exec(`bash -c "free -m | awk '/^Mem:/ {printf \"%.1f\", $2/1024}'"`)
|
|
const pct = Math.round((parseFloat(used) / parseFloat(total)) * 100);
|
|
self.label = ` ${used}G`;
|
|
self.toggleClassName("warning", pct > 70);
|
|
self.toggleClassName("critical", pct > 90);
|
|
}]],
|
|
});
|
|
|
|
// ── Network ─────────────────────────────────────────────────────────────────
|
|
|
|
const Network = () => Widget.Label({
|
|
className: "network",
|
|
connections: [[network, (self) => {
|
|
if (network.primary === "wifi") {
|
|
const wifi = network.wifi;
|
|
self.label = ` ${wifi?.ssid || ""}`;
|
|
self.toggleClassName("wifi", true);
|
|
self.toggleClassName("disconnected", false);
|
|
} else if (network.primary === "wired") {
|
|
self.label = " Eth";
|
|
self.toggleClassName("wifi", false);
|
|
self.toggleClassName("disconnected", false);
|
|
} else {
|
|
self.label = " ";
|
|
self.toggleClassName("disconnected", true);
|
|
}
|
|
}]],
|
|
});
|
|
|
|
// ── Volume ──────────────────────────────────────────────────────────────────
|
|
|
|
const Volume = () => Widget.Button({
|
|
className: "volume",
|
|
onClicked: () => { audio.speaker.isMuted = !audio.speaker.isMuted; },
|
|
child: Widget.Label({
|
|
connections: [[audio, (self) => {
|
|
const vol = Math.round((audio.speaker?.volume || 0) * 100);
|
|
const muted = audio.speaker?.isMuted;
|
|
const icon = muted ? "" : vol > 66 ? "" : vol > 33 ? "" : "";
|
|
self.label = `${icon} ${vol}%`;
|
|
self.parent?.toggleClassName("muted", muted);
|
|
}, "speaker-changed"]],
|
|
}),
|
|
});
|
|
|
|
// ── Battery ─────────────────────────────────────────────────────────────────
|
|
|
|
const Battery = () => Widget.Label({
|
|
className: "battery",
|
|
visible: battery.bind("available"),
|
|
connections: [[battery, (self) => {
|
|
const pct = battery.percent;
|
|
const charging = battery.charging;
|
|
const icon = charging ? "" : pct > 80 ? "" : pct > 60 ? "" :
|
|
pct > 40 ? "" : pct > 20 ? "" : "";
|
|
self.label = `${icon} ${pct}%`;
|
|
self.toggleClassName("charging", charging);
|
|
self.toggleClassName("low", pct <= 20 && !charging);
|
|
self.toggleClassName("warning", pct <= 30 && !charging);
|
|
}]],
|
|
});
|
|
|
|
// ── Media ───────────────────────────────────────────────────────────────────
|
|
|
|
const Media = () => Widget.Label({
|
|
className: "media",
|
|
connections: [[mpris, (self) => {
|
|
const player = mpris.players[0];
|
|
if (!player) {
|
|
self.visible = false;
|
|
return;
|
|
}
|
|
self.visible = true;
|
|
const artist = player.trackArtists?.join(", ") || "";
|
|
const title = player.trackTitle || "";
|
|
const icon = player.playBackStatus === "Playing" ? " " : " ";
|
|
self.label = `${icon}${artist ? artist + " — " : ""}${title}`.slice(0, 50);
|
|
self.toggleClassName("paused", player.playBackStatus !== "Playing");
|
|
}]],
|
|
});
|
|
|
|
// ── Systray ─────────────────────────────────────────────────────────────────
|
|
|
|
const SysTray = () => Widget.Box({
|
|
className: "systray",
|
|
children: systemtray.bind("items").transform((items) =>
|
|
items.map((item) =>
|
|
Widget.Button({
|
|
child: Widget.Icon({ icon: item.bind("icon"), size: 16 }),
|
|
tooltipMarkup: item.bind("tooltip-markup"),
|
|
onPrimaryClick: (_, event) => item.activate(event),
|
|
onSecondaryClick: (_, event) => item.openMenu(event),
|
|
})
|
|
)
|
|
),
|
|
});
|
|
|
|
// ── Launcher button ─────────────────────────────────────────────────────────
|
|
|
|
const LauncherBtn = () => Widget.Button({
|
|
className: "launcher-btn",
|
|
label: "",
|
|
onClicked: () => App.toggleWindow("launcher"),
|
|
});
|
|
|
|
// ── Power button ────────────────────────────────────────────────────────────
|
|
|
|
const PowerBtn = () => Widget.Button({
|
|
className: "power-btn",
|
|
label: "⏻",
|
|
onClicked: () => Utils.exec("bash -c 'systemctl poweroff'"),
|
|
});
|
|
|
|
// ── Workspaces (Hyprland only) ──────────────────────────────────────────────
|
|
|
|
const Workspaces = (compositor) => {
|
|
if (compositor !== "hyprland") return Widget.Box({});
|
|
|
|
const hyprland = await Service.import("hyprland");
|
|
return Widget.Box({
|
|
className: "workspaces",
|
|
children: hyprland.bind("workspaces").transform((ws) =>
|
|
ws.sort((a, b) => a.id - b.id)
|
|
.filter((w) => w.id > 0)
|
|
.map((w) =>
|
|
Widget.Button({
|
|
onClicked: () => hyprland.messageAsync(`dispatch workspace ${w.id}`),
|
|
child: Widget.Label({ label: `${w.id}` }),
|
|
className: hyprland.active.workspace.bind("id").transform(
|
|
(id) => id === w.id ? "active" : w.windows > 0 ? "occupied" : ""
|
|
),
|
|
})
|
|
)
|
|
),
|
|
});
|
|
};
|
|
|
|
// ── Bar assembly ────────────────────────────────────────────────────────────
|
|
|
|
export const Bar = (monitor, compositor) => Widget.Window({
|
|
name: `bar-${monitor}`,
|
|
monitor,
|
|
anchor: ["top", "left", "right"],
|
|
exclusivity: "exclusive",
|
|
className: "bar",
|
|
child: Widget.CenterBox({
|
|
startWidget: Widget.Box({
|
|
className: "modules-left",
|
|
children: [
|
|
LauncherBtn(),
|
|
Separator(),
|
|
Workspaces(compositor),
|
|
Separator(),
|
|
CPU(),
|
|
RAM(),
|
|
],
|
|
}),
|
|
centerWidget: Widget.Box({
|
|
className: "modules-center",
|
|
children: [
|
|
Media(),
|
|
],
|
|
}),
|
|
endWidget: Widget.Box({
|
|
className: "modules-right",
|
|
hpack: "end",
|
|
children: [
|
|
Network(),
|
|
Separator(),
|
|
Volume(),
|
|
Separator(),
|
|
Battery(),
|
|
Separator(),
|
|
DateWidget(),
|
|
Clock(),
|
|
Separator(),
|
|
SysTray(),
|
|
Separator(),
|
|
PowerBtn(),
|
|
],
|
|
}),
|
|
}),
|
|
});
|