Compare commits
5 Commits
932b174c36
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 29b4c54370 | |||
| 9eaaa01663 | |||
| e94b841b2a | |||
| 51b725e1f7 | |||
| da22b6446d |
@@ -7,11 +7,14 @@ import BrainPower from "./widget/panels/BrainPower"
|
|||||||
app.start({
|
app.start({
|
||||||
css: style,
|
css: style,
|
||||||
main() {
|
main() {
|
||||||
for (const monitor of app.get_monitors()) {
|
const monitors = app.get_monitors()
|
||||||
|
for (const monitor of monitors) {
|
||||||
Heartbeat(monitor)
|
Heartbeat(monitor)
|
||||||
Bar(monitor)
|
Bar(monitor)
|
||||||
BrainPower(monitor)
|
|
||||||
}
|
}
|
||||||
|
// Brain Power on primary monitor only
|
||||||
|
const primary = monitors[0]
|
||||||
|
if (primary) BrainPower(primary)
|
||||||
},
|
},
|
||||||
requestHandler(request: any, res: (response: any) => void) {
|
requestHandler(request: any, res: (response: any) => void) {
|
||||||
const cmd = String(request)
|
const cmd = String(request)
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import GLib from "gi://GLib"
|
import GLib from "gi://GLib"
|
||||||
|
import { exec } from "ags/process"
|
||||||
|
|
||||||
const BRAIN_ROOT = "/home/tetardtek/Dev/Cortex-Template"
|
const BRAIN_ROOT = GLib.getenv("BRAIN_ROOT")
|
||||||
|
|| readFile(GLib.get_home_dir() + "/.config/brain-path")?.trim()
|
||||||
|
|| GLib.get_home_dir() + "/Dev/Brain"
|
||||||
|
|
||||||
export interface BrainState {
|
export interface BrainState {
|
||||||
focus: string
|
focus: string
|
||||||
todos: { open: number; done: number; top3: string[] }
|
todos: { open: number; done: number; top3: string[] }
|
||||||
session: string | null
|
session: string | null
|
||||||
|
intentions: { active: number; names: string[] }
|
||||||
|
repos: { name: string; status: string }[]
|
||||||
|
commits: string[]
|
||||||
|
kernelVersion: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function readFile(path: string): string | null {
|
function readFile(path: string): string | null {
|
||||||
@@ -18,10 +25,13 @@ function readFile(path: string): string | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sh(cmd: string): string {
|
||||||
|
try { return exec(`bash -c "${cmd}"`).trim() } catch { return "" }
|
||||||
|
}
|
||||||
|
|
||||||
export function getFocus(): string {
|
export function getFocus(): string {
|
||||||
const content = readFile(`${BRAIN_ROOT}/focus.md`)
|
const content = readFile(`${BRAIN_ROOT}/focus.md`)
|
||||||
if (!content) return "no focus"
|
if (!content) return "no focus"
|
||||||
// skip header lines, get the meat
|
|
||||||
const lines = content.split("\n").filter(
|
const lines = content.split("\n").filter(
|
||||||
(l) => !l.startsWith("#") && !l.startsWith(">") && l.trim() !== "" && !l.startsWith("---")
|
(l) => !l.startsWith("#") && !l.startsWith(">") && l.trim() !== "" && !l.startsWith("---")
|
||||||
)
|
)
|
||||||
@@ -45,11 +55,7 @@ export function getTodos(): { open: number; done: number; top3: string[] } {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { open: open.length, done, top3: open.slice(0, 3) }
|
||||||
open: open.length,
|
|
||||||
done,
|
|
||||||
top3: open.slice(0, 3),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSession(): string | null {
|
export function getSession(): string | null {
|
||||||
@@ -64,10 +70,53 @@ export function getSession(): string | null {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIntentions(): { active: number; names: string[] } {
|
||||||
|
try {
|
||||||
|
const out = sh(`grep -rl 'status: active' ${BRAIN_ROOT}/intentions/*.yml 2>/dev/null | while read f; do grep '^name:' "$f" | head -1 | sed 's/name: //'; done`)
|
||||||
|
const names = out.split("\n").filter(Boolean)
|
||||||
|
return { active: names.length, names: names.slice(0, 5) }
|
||||||
|
} catch {
|
||||||
|
return { active: 0, names: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepoStatus(): { name: string; status: string }[] {
|
||||||
|
const repos = [
|
||||||
|
{ name: "brain", path: BRAIN_ROOT },
|
||||||
|
{ name: "toolkit", path: `${BRAIN_ROOT}/toolkit` },
|
||||||
|
{ name: "progression", path: `${BRAIN_ROOT}/progression` },
|
||||||
|
]
|
||||||
|
|
||||||
|
return repos.map(({ name, path }) => {
|
||||||
|
const count = sh(`git -C ${path} status --short 2>/dev/null | wc -l`)
|
||||||
|
const n = parseInt(count) || 0
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
status: n === 0 ? "✅" : `⚠️ ${n} fichiers`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRecentCommits(): string[] {
|
||||||
|
const out = sh(`git -C ${BRAIN_ROOT} log --oneline -5 2>/dev/null`)
|
||||||
|
return out.split("\n").filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKernelVersion(): string {
|
||||||
|
const content = readFile(`${BRAIN_ROOT}/brain-compose.yml`)
|
||||||
|
if (!content) return "?"
|
||||||
|
const match = content.match(/^version:\s*"?([^"\n]+)"?/m)
|
||||||
|
return match ? match[1] : "?"
|
||||||
|
}
|
||||||
|
|
||||||
export function getBrainState(): BrainState {
|
export function getBrainState(): BrainState {
|
||||||
return {
|
return {
|
||||||
focus: getFocus(),
|
focus: getFocus(),
|
||||||
todos: getTodos(),
|
todos: getTodos(),
|
||||||
session: getSession(),
|
session: getSession(),
|
||||||
|
intentions: getIntentions(),
|
||||||
|
repos: getRepoStatus(),
|
||||||
|
commits: getRecentCommits(),
|
||||||
|
kernelVersion: getKernelVersion(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,208 +1,13 @@
|
|||||||
@use "styles/palette" as *;
|
@use "styles/palette" as *;
|
||||||
|
@use "styles/heartbeat";
|
||||||
|
@use "styles/bar";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
font-family: $font;
|
font-family: $font;
|
||||||
font-size: 13px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Layer 0 — Heartbeat ──
|
|
||||||
window.Heartbeat {
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
.heartbeat-line {
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
rgba(255, 77, 166, 0.0),
|
|
||||||
rgba(255, 77, 166, 0.6),
|
|
||||||
rgba(201, 160, 255, 0.8),
|
|
||||||
rgba(255, 77, 166, 0.6),
|
|
||||||
rgba(255, 77, 166, 0.0)
|
|
||||||
);
|
|
||||||
min-height: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Layer 1 — Ghost Bar ──
|
|
||||||
window.Bar {
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
> centerbox {
|
|
||||||
background: rgba(38, 21, 55, 0.88);
|
|
||||||
border-radius: $radius;
|
|
||||||
border: 3px solid rgba(255, 77, 166, 0.60);
|
|
||||||
margin: 6px 8px;
|
|
||||||
padding: 0 8px;
|
|
||||||
min-height: 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modules-left,
|
|
||||||
.modules-center,
|
|
||||||
.modules-right {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module {
|
|
||||||
padding: 0 8px;
|
|
||||||
color: $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-name {
|
|
||||||
color: $magenta;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-cursor {
|
|
||||||
color: $lilac;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
color: rgba(240, 234, 248, 0.12);
|
|
||||||
font-size: 11px;
|
|
||||||
padding: 0 4px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── clock ──
|
|
||||||
.clock {
|
|
||||||
color: $magenta;
|
|
||||||
font-weight: 900;
|
|
||||||
font-size: 14px;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date {
|
|
||||||
color: $lavande;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0 10px 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── system stats ──
|
|
||||||
.cpu {
|
|
||||||
color: $lavande;
|
|
||||||
&.warning { color: $champagne; }
|
|
||||||
&.critical { color: $danger; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.ram {
|
|
||||||
color: $magenta;
|
|
||||||
&.warning { color: $champagne; }
|
|
||||||
&.critical { color: $danger; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── network ──
|
|
||||||
.network {
|
|
||||||
color: $lavande;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 0 8px;
|
|
||||||
&.disconnected { color: $danger; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── volume ──
|
|
||||||
.volume {
|
|
||||||
color: $magenta;
|
|
||||||
padding: 0 8px;
|
|
||||||
&.muted { color: rgba(255, 77, 166, 0.30); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── battery ──
|
|
||||||
.battery {
|
|
||||||
color: $magenta;
|
|
||||||
padding: 0 8px;
|
|
||||||
&.charging { color: $mitsuri; }
|
|
||||||
&.low { color: $danger; }
|
|
||||||
&.warning { color: $champagne; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── hover effects ──
|
|
||||||
.module:hover,
|
|
||||||
.cpu:hover,
|
|
||||||
.ram:hover,
|
|
||||||
.network:hover,
|
|
||||||
.battery:hover {
|
|
||||||
color: $lilac;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── systray ──
|
|
||||||
.systray {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.systray-item {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0 3px;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(201, 160, 255, 0.12);
|
|
||||||
border-radius: $radius-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── volume interactive ──
|
|
||||||
button.volume,
|
|
||||||
button.muted {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0 8px;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
label { color: $magenta; }
|
|
||||||
&.muted label { color: rgba(255, 77, 166, 0.30); }
|
|
||||||
&:hover label { color: $lilac; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── media ──
|
|
||||||
.media-module {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media.paused {
|
|
||||||
.media-text {
|
|
||||||
color: rgba(201, 160, 255, 0.50);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-text {
|
|
||||||
color: $lilac;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-prev,
|
|
||||||
.media-play,
|
|
||||||
.media-next {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0 3px;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: $lilac;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover label {
|
|
||||||
color: $magenta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Layer 3 — Brain Power Panel ──
|
// ── Layer 3 — Brain Power Panel ──
|
||||||
window.BrainPower {
|
window.BrainPower {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -291,10 +96,39 @@ window.BrainPower {
|
|||||||
padding: 4px 0 4px 16px;
|
padding: 4px 0 4px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brain-terminal-placeholder {
|
.brain-version {
|
||||||
color: $muted;
|
color: $muted;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
font-style: italic;
|
font-weight: normal;
|
||||||
padding: 8px 0 4px 16px;
|
}
|
||||||
|
|
||||||
|
.brain-intentions-list {
|
||||||
|
color: $mitsuri;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 4px 0 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brain-repos-status {
|
||||||
|
color: $subtext1;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
|
font-family: "Maple Mono NF", monospace;
|
||||||
|
padding: 4px 0 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brain-commits-list {
|
||||||
|
color: $subtext0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
font-family: "Maple Mono NF", monospace;
|
||||||
|
padding: 4px 0 4px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brain-terminal {
|
||||||
|
background: #1a0e27;
|
||||||
|
border-radius: 0 0 $radius 0;
|
||||||
|
padding: 4px;
|
||||||
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@use "palette" as *;
|
@use "palette" as *;
|
||||||
|
|
||||||
// Layer 1 — Ghost bar (hover reveal)
|
// Layer 1 — Ghost bar (hover reveal)
|
||||||
// GTK CSS alpha() must be unquoted strings to pass through SCSS compiler
|
|
||||||
|
|
||||||
window.Bar {
|
window.Bar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -12,7 +11,7 @@ window.Bar {
|
|||||||
border: 3px solid rgba(255, 77, 166, 0.60); // $magenta @ 0.60
|
border: 3px solid rgba(255, 77, 166, 0.60); // $magenta @ 0.60
|
||||||
margin: 6px 8px;
|
margin: 6px 8px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
min-height: 38px;
|
min-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modules-left,
|
.modules-left,
|
||||||
@@ -24,16 +23,24 @@ window.Bar {
|
|||||||
.module {
|
.module {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
color: $text;
|
color: $text;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $lilac;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prompt-name {
|
||||||
|
color: $magenta;
|
||||||
|
font-size: 17px;
|
||||||
|
padding: 0 0 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-cursor {
|
||||||
|
color: $lilac;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
color: rgba(240, 234, 248, 0.12); // $text @ 0.12
|
color: rgba(240, 234, 248, 0.12); // $text @ 0.12
|
||||||
font-size: 11px;
|
font-size: 14px;
|
||||||
padding: 0 4px;
|
padding: 0 6px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,21 +48,19 @@ window.Bar {
|
|||||||
.clock {
|
.clock {
|
||||||
color: $magenta;
|
color: $magenta;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 14px;
|
font-size: 18px;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
&:hover { color: $lilac; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
color: $lavande;
|
color: $lavande;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding: 0 10px 0 2px;
|
padding: 0 10px 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── system ──
|
// ── system stats ──
|
||||||
.cpu { color: $lavande; }
|
.cpu { color: $lavande; }
|
||||||
.ram { color: $magenta; }
|
.ram { color: $magenta; }
|
||||||
.temp {
|
.temp {
|
||||||
@@ -64,7 +69,9 @@ window.Bar {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpu, .ram, .temp {
|
.gpu { color: $mitsuri; }
|
||||||
|
|
||||||
|
.cpu, .ram, .gpu, .temp {
|
||||||
&.warning { color: $champagne; }
|
&.warning { color: $champagne; }
|
||||||
&.critical { color: $danger; }
|
&.critical { color: $danger; }
|
||||||
}
|
}
|
||||||
@@ -72,40 +79,94 @@ window.Bar {
|
|||||||
// ── network ──
|
// ── network ──
|
||||||
.network {
|
.network {
|
||||||
color: $lavande;
|
color: $lavande;
|
||||||
font-size: 12px;
|
font-size: 15px;
|
||||||
|
padding: 0 10px;
|
||||||
&.disconnected { color: $danger; }
|
&.disconnected { color: $danger; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── volume ──
|
// ── volume ──
|
||||||
.volume {
|
button.volume,
|
||||||
color: $magenta;
|
button.muted {
|
||||||
&.muted { color: rgba(255, 77, 166, 0.30); }
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0 8px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
label { color: $magenta; }
|
||||||
|
&.muted label { color: rgba(255, 77, 166, 0.30); }
|
||||||
|
&:hover label { color: $lilac; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── battery ──
|
// ── battery ──
|
||||||
.battery {
|
.battery {
|
||||||
color: $magenta;
|
color: $magenta;
|
||||||
|
padding: 0 8px;
|
||||||
&.charging { color: $mitsuri; }
|
&.charging { color: $mitsuri; }
|
||||||
&.low { color: $danger; }
|
&.low { color: $danger; }
|
||||||
&.warning { color: $champagne; }
|
&.warning { color: $champagne; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── media ──
|
// ── media ──
|
||||||
.media {
|
.media-module {
|
||||||
color: $lilac;
|
padding: 0 4px;
|
||||||
font-size: 12px;
|
}
|
||||||
font-weight: normal;
|
|
||||||
padding: 0 10px;
|
|
||||||
|
|
||||||
&.paused {
|
.media {
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
&.paused .media-text {
|
||||||
color: rgba(201, 160, 255, 0.50); // $lilac @ 0.50
|
color: rgba(201, 160, 255, 0.50); // $lilac @ 0.50
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-text {
|
||||||
|
color: $lilac;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-prev,
|
||||||
|
.media-play,
|
||||||
|
.media-next {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0 3px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
label { color: $lilac; font-size: 16px; }
|
||||||
|
&:hover label { color: $magenta; }
|
||||||
|
}
|
||||||
|
|
||||||
// ── systray ──
|
// ── systray ──
|
||||||
.systray {
|
.systray {
|
||||||
padding: 0 6px;
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.systray-item {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0 3px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(201, 160, 255, 0.12);
|
||||||
|
border-radius: $radius-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── hover effects ──
|
||||||
|
.module:hover,
|
||||||
|
.clock:hover,
|
||||||
|
.cpu:hover,
|
||||||
|
.ram:hover,
|
||||||
|
.network:hover,
|
||||||
|
.battery:hover {
|
||||||
|
color: $lilac;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── workspaces ──
|
// ── workspaces ──
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Toggle Brain Power panel via AGS IPC
|
# Toggle Brain Power — dashboard AGS + terminal Kitty (single instance)
|
||||||
ags request "toggle-brain" 2>/dev/null
|
BRAIN_ROOT="${BRAIN_ROOT:-$HOME/Dev/Brain}"
|
||||||
|
KITTY_CLASS="brain-hud-terminal"
|
||||||
|
|
||||||
|
# Check if brain kitty is already running (by window class)
|
||||||
|
kitty_pid=$(pgrep -f "class $KITTY_CLASS" | head -1)
|
||||||
|
|
||||||
|
if [ -n "$kitty_pid" ]; then
|
||||||
|
# Close everything
|
||||||
|
ags request "toggle-brain" 2>/dev/null
|
||||||
|
kill "$kitty_pid" 2>/dev/null
|
||||||
|
else
|
||||||
|
# Open everything
|
||||||
|
ags request "toggle-brain" 2>/dev/null
|
||||||
|
kitty \
|
||||||
|
--class "$KITTY_CLASS" \
|
||||||
|
--title "🧠 Brain HUD" \
|
||||||
|
--override remember_window_size=no \
|
||||||
|
--override initial_window_width=60c \
|
||||||
|
--override initial_window_height=30c \
|
||||||
|
--override background_opacity=0.94 \
|
||||||
|
--override background=#1a0e27 \
|
||||||
|
--override foreground=#f0eaf8 \
|
||||||
|
--override cursor=#ff4da6 \
|
||||||
|
--override font_size=13 \
|
||||||
|
--override confirm_os_window_close=0 \
|
||||||
|
--directory "$BRAIN_ROOT" \
|
||||||
|
-e zsh -c "echo '🧠 Brain HUD — navigate mode'; echo ''; exec zsh" \
|
||||||
|
&
|
||||||
|
disown
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import app from "ags/gtk3/app"
|
import app from "ags/gtk3/app"
|
||||||
import { Astal, Gtk, Gdk } from "ags/gtk3"
|
import { Astal, Gtk, Gdk } from "ags/gtk3"
|
||||||
import Clock from "./modules/Clock"
|
import Clock from "./modules/Clock"
|
||||||
import Battery from "./modules/Battery"
|
// import Battery from "./modules/Battery" // desktop — no battery
|
||||||
import Volume from "./modules/Volume"
|
import Volume from "./modules/Volume"
|
||||||
import Network from "./modules/Network"
|
import Network from "./modules/Network"
|
||||||
import SystemStats from "./modules/SystemStats"
|
import SystemStats from "./modules/SystemStats"
|
||||||
import Media from "./modules/Media"
|
import Media from "./modules/Media"
|
||||||
import SysTray from "./modules/SysTray"
|
// import SysTray from "./modules/SysTray" // TODO: needs astal-tray (appmenu-glib-translator)
|
||||||
import Prompt from "./modules/Prompt"
|
import Prompt from "./modules/Prompt"
|
||||||
|
|
||||||
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
||||||
@@ -33,7 +33,7 @@ export default function Bar(gdkmonitor: Gdk.Monitor) {
|
|||||||
<window
|
<window
|
||||||
class="Bar"
|
class="Bar"
|
||||||
name="bar"
|
name="bar"
|
||||||
visible={false}
|
visible={true}
|
||||||
gdkmonitor={gdkmonitor}
|
gdkmonitor={gdkmonitor}
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
anchor={TOP | LEFT | RIGHT}
|
anchor={TOP | LEFT | RIGHT}
|
||||||
@@ -42,9 +42,8 @@ export default function Bar(gdkmonitor: Gdk.Monitor) {
|
|||||||
>
|
>
|
||||||
<eventbox
|
<eventbox
|
||||||
onHover={() => cancelHide()}
|
onHover={() => cancelHide()}
|
||||||
onHoverLost={(self) => {
|
onHoverLost={(_self) => {
|
||||||
const win = self.get_toplevel() as Astal.Window
|
// disabled for debug — auto-hide off
|
||||||
scheduleHide(win)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<centerbox>
|
<centerbox>
|
||||||
@@ -59,13 +58,10 @@ export default function Bar(gdkmonitor: Gdk.Monitor) {
|
|||||||
<Clock />
|
<Clock />
|
||||||
</box>
|
</box>
|
||||||
<box $type="end" class="modules-right" halign={Gtk.Align.END}>
|
<box $type="end" class="modules-right" halign={Gtk.Align.END}>
|
||||||
<SysTray />
|
{/* <SysTray /> */}
|
||||||
<label class="separator" label="│" />
|
|
||||||
<Network />
|
<Network />
|
||||||
<label class="separator" label="│" />
|
<label class="separator" label="│" />
|
||||||
<Volume />
|
<Volume />
|
||||||
<label class="separator" label="│" />
|
|
||||||
<Battery />
|
|
||||||
</box>
|
</box>
|
||||||
</centerbox>
|
</centerbox>
|
||||||
</eventbox>
|
</eventbox>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function Heartbeat(gdkmonitor: Gdk.Monitor) {
|
|||||||
exclusivity={Astal.Exclusivity.NONE}
|
exclusivity={Astal.Exclusivity.NONE}
|
||||||
anchor={TOP | LEFT | RIGHT}
|
anchor={TOP | LEFT | RIGHT}
|
||||||
application={app}
|
application={app}
|
||||||
layer={Astal.Layer.OVERLAY}
|
layer={Astal.Layer.TOP}
|
||||||
>
|
>
|
||||||
<eventbox
|
<eventbox
|
||||||
onHover={() => {
|
onHover={() => {
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ export default function Battery() {
|
|||||||
|
|
||||||
const text = createBinding(bat, "percentage")((p: number) => {
|
const text = createBinding(bat, "percentage")((p: number) => {
|
||||||
const pct = Math.round(p * 100)
|
const pct = Math.round(p * 100)
|
||||||
|
const isCharging = bat.charging
|
||||||
let icon = ""
|
let icon = ""
|
||||||
if (bat.charging) icon = ""
|
if (isCharging) icon = ""
|
||||||
else if (pct > 90) icon = ""
|
else if (pct > 90) icon = ""
|
||||||
else if (pct > 70) icon = ""
|
else if (pct > 70) icon = ""
|
||||||
else if (pct > 50) icon = ""
|
else if (pct > 50) icon = ""
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createPoll } from "ags/time"
|
import { createPoll } from "ags/time"
|
||||||
|
import { exec } from "ags/process"
|
||||||
|
|
||||||
function formatTime(): string {
|
function formatTime(): string {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@@ -10,6 +11,16 @@ function formatTime(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(): string {
|
function formatDate(): string {
|
||||||
|
const now = new Date()
|
||||||
|
return now.toLocaleDateString("fr-FR", {
|
||||||
|
weekday: "long",
|
||||||
|
day: "numeric",
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateShort(): string {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
return now.toLocaleDateString("fr-FR", {
|
return now.toLocaleDateString("fr-FR", {
|
||||||
weekday: "short",
|
weekday: "short",
|
||||||
@@ -18,15 +29,44 @@ function formatDate(): string {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCalendar(): string {
|
||||||
|
try {
|
||||||
|
return exec("bash -c \"cal -h\"").trim()
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUptime(): string {
|
||||||
|
try {
|
||||||
|
return exec("bash -c \"uptime -p | sed 's/up //'\"").trim()
|
||||||
|
} catch {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTooltip(): string {
|
||||||
|
const full = formatDate()
|
||||||
|
const cal = getCalendar()
|
||||||
|
const up = getUptime()
|
||||||
|
return `${full}\n\n${cal}\n\n⏱ uptime: ${up}`
|
||||||
|
}
|
||||||
|
|
||||||
export default function Clock() {
|
export default function Clock() {
|
||||||
const time = createPoll("", 1000, () => formatTime())
|
const time = createPoll("", 1000, () => formatTime())
|
||||||
const date = createPoll("", 60000, () => formatDate())
|
const date = createPoll("", 60000, () => formatDateShort())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box>
|
<box>
|
||||||
<label class="clock" label={time} />
|
<label class="clock" label={time} />
|
||||||
<label class="separator" label="│" />
|
<label class="separator" label="│" />
|
||||||
<label class="date" label={date} />
|
<eventbox
|
||||||
|
onHover={(self) => {
|
||||||
|
self.tooltipText = buildTooltip()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label class="date" label={date} hasTooltip />
|
||||||
|
</eventbox>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,27 @@ export default function Media() {
|
|||||||
const mpris = AstalMpris.get_default()
|
const mpris = AstalMpris.get_default()
|
||||||
const players = createBinding(mpris, "players")
|
const players = createBinding(mpris, "players")
|
||||||
|
|
||||||
|
// Filter to only show non-playerctld players
|
||||||
|
const filtered = players((list: AstalMpris.Player[]) =>
|
||||||
|
list.filter((p) => p.busName && !p.busName.includes("playerctld")).slice(0, 1)
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box class="media-module">
|
<box class="media-module">
|
||||||
<For each={players}>
|
<For each={filtered}>
|
||||||
{(player) => {
|
{(player) => {
|
||||||
const title = createBinding(player, "title")
|
const title = createBinding(player, "title")
|
||||||
const artist = createBinding(player, "artist")
|
|
||||||
const status = createBinding(player, "playbackStatus")
|
const status = createBinding(player, "playbackStatus")
|
||||||
|
|
||||||
const icon = status((s: AstalMpris.PlaybackStatus) =>
|
const icon = status((s: AstalMpris.PlaybackStatus) =>
|
||||||
s === AstalMpris.PlaybackStatus.PLAYING ? "" : ""
|
s === AstalMpris.PlaybackStatus.PLAYING ? "▶" : "⏸"
|
||||||
)
|
)
|
||||||
|
|
||||||
const label = title((t: string) => {
|
const label = title((t: string) => {
|
||||||
const a = player.artist
|
const a = player.artist
|
||||||
const display = a ? `${a} — ${t}` : t
|
const display = a ? `${a} — ${t}` : t
|
||||||
// truncate long titles
|
return display.length > 45
|
||||||
return display.length > 40
|
? display.substring(0, 42) + "..."
|
||||||
? display.substring(0, 37) + "..."
|
|
||||||
: display
|
: display
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,23 +35,14 @@ export default function Media() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<box class={cls}>
|
<box class={cls}>
|
||||||
<button
|
<button class="media-prev" onClicked={() => player.previous()}>
|
||||||
class="media-prev"
|
<label label="⏮" />
|
||||||
onClicked={() => player.previous()}
|
|
||||||
>
|
|
||||||
<label label="" />
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="media-play" onClicked={() => player.play_pause()}>
|
||||||
class="media-play"
|
|
||||||
onClicked={() => player.play_pause()}
|
|
||||||
>
|
|
||||||
<label label={icon} />
|
<label label={icon} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="media-next" onClicked={() => player.next()}>
|
||||||
class="media-next"
|
<label label="⏭" />
|
||||||
onClicked={() => player.next()}
|
|
||||||
>
|
|
||||||
<label label="" />
|
|
||||||
</button>
|
</button>
|
||||||
<label class="media-text" label={label} />
|
<label class="media-text" label={label} />
|
||||||
</box>
|
</box>
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import AstalNetwork from "gi://AstalNetwork"
|
import AstalNetwork from "gi://AstalNetwork"
|
||||||
import { createBinding } from "ags"
|
import { createBinding } from "ags"
|
||||||
|
import { exec } from "ags/process"
|
||||||
|
|
||||||
|
function getIPs(): string {
|
||||||
|
try {
|
||||||
|
const lan = exec("bash -c \"hostname -I | awk '{print $1}'\"").trim()
|
||||||
|
const wan = exec("bash -c \"curl -4 -s --max-time 2 ifconfig.me\"").trim()
|
||||||
|
return `LAN: ${lan || "?"}\nWAN: ${wan || "?"}`
|
||||||
|
} catch {
|
||||||
|
return "IPs unavailable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Network() {
|
export default function Network() {
|
||||||
const net = AstalNetwork.get_default()
|
const net = AstalNetwork.get_default()
|
||||||
@@ -17,15 +28,23 @@ export default function Network() {
|
|||||||
else if (strength > 20) icon = ""
|
else if (strength > 20) icon = ""
|
||||||
return `${icon} ${ssid}`
|
return `${icon} ${ssid}`
|
||||||
}
|
}
|
||||||
if (type === AstalNetwork.Primary.WIRED) return " eth"
|
if (type === AstalNetwork.Primary.WIRED) return " wired"
|
||||||
return " offline"
|
return " offline"
|
||||||
})
|
})
|
||||||
|
|
||||||
const cls = createBinding(net, "primary")((type: AstalNetwork.Primary) => {
|
const cls = createBinding(net, "primary")((type: AstalNetwork.Primary) => {
|
||||||
if (type === AstalNetwork.Primary.WIFI) return "network wifi"
|
if (type === AstalNetwork.Primary.WIFI) return "network module wifi"
|
||||||
if (type === AstalNetwork.Primary.WIRED) return "network wired"
|
if (type === AstalNetwork.Primary.WIRED) return "network module wired"
|
||||||
return "network disconnected"
|
return "network module disconnected"
|
||||||
})
|
})
|
||||||
|
|
||||||
return <label class={cls} label={text} />
|
return (
|
||||||
|
<eventbox
|
||||||
|
onHover={(self) => {
|
||||||
|
self.tooltipText = getIPs()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label class={cls} label={text} hasTooltip />
|
||||||
|
</eventbox>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import GLib from "gi://GLib"
|
||||||
import { createPoll } from "ags/time"
|
import { createPoll } from "ags/time"
|
||||||
|
|
||||||
export default function Prompt() {
|
export default function Prompt() {
|
||||||
|
const username = GLib.get_user_name() || "user"
|
||||||
|
|
||||||
const cursor = createPoll("_", 600, () => {
|
const cursor = createPoll("_", 600, () => {
|
||||||
// alternate between _ and empty to create blink
|
// alternate between _ and empty to create blink
|
||||||
return Date.now() % 1200 < 600 ? "_" : " "
|
return Date.now() % 1200 < 600 ? "_" : " "
|
||||||
@@ -8,7 +11,7 @@ export default function Prompt() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<box>
|
<box>
|
||||||
<label class="prompt-name" label="tetardtek" />
|
<label class="prompt-name" label={username} />
|
||||||
<label class="prompt-cursor" label={cursor} />
|
<label class="prompt-cursor" label={cursor} />
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +1,89 @@
|
|||||||
|
import GLib from "gi://GLib"
|
||||||
import { createPoll } from "ags/time"
|
import { createPoll } from "ags/time"
|
||||||
import { exec } from "ags/process"
|
import { exec } from "ags/process"
|
||||||
|
|
||||||
function getCpuUsage(): string {
|
function readProc(path: string): string | null {
|
||||||
try {
|
try {
|
||||||
const out = exec("bash -c \"top -bn1 | grep 'Cpu(s)' | awk '{print 100 - $8}'\"")
|
const [ok, contents] = GLib.file_get_contents(path)
|
||||||
return `${Math.round(parseFloat(out))}`
|
if (ok && contents) return new TextDecoder().decode(contents)
|
||||||
} catch {
|
} catch {}
|
||||||
return "?"
|
return null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRamUsage(): string {
|
let prevIdle = 0
|
||||||
|
let prevTotal = 0
|
||||||
|
|
||||||
|
function getCpuUsage(): string {
|
||||||
|
const content = readProc("/proc/stat")
|
||||||
|
if (!content) return "?"
|
||||||
|
|
||||||
|
const line = content.split("\n")[0]
|
||||||
|
const parts = line.split(/\s+/).slice(1).map(Number)
|
||||||
|
const idle = parts[3] + parts[4]
|
||||||
|
const total = parts.reduce((a, b) => a + b, 0)
|
||||||
|
|
||||||
|
const dIdle = idle - prevIdle
|
||||||
|
const dTotal = total - prevTotal
|
||||||
|
prevIdle = idle
|
||||||
|
prevTotal = total
|
||||||
|
|
||||||
|
if (dTotal === 0) return "0"
|
||||||
|
return `${Math.round(((dTotal - dIdle) / dTotal) * 100)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRamUsage(): { pct: string; used: string; total: string } {
|
||||||
|
const content = readProc("/proc/meminfo")
|
||||||
|
if (!content) return { pct: "?", used: "?", total: "?" }
|
||||||
|
|
||||||
|
const lines = content.split("\n")
|
||||||
|
let totalKb = 0, availableKb = 0
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith("MemTotal:")) totalKb = parseInt(line.split(/\s+/)[1])
|
||||||
|
if (line.startsWith("MemAvailable:")) availableKb = parseInt(line.split(/\s+/)[1])
|
||||||
|
if (totalKb && availableKb) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!totalKb) return { pct: "?", used: "?", total: "?" }
|
||||||
|
const usedGb = ((totalKb - availableKb) / 1048576).toFixed(1)
|
||||||
|
const totalGb = (totalKb / 1048576).toFixed(0)
|
||||||
|
const pct = `${Math.round(((totalKb - availableKb) / totalKb) * 100)}`
|
||||||
|
return { pct, used: usedGb, total: totalGb }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGpuInfo(): string {
|
||||||
try {
|
try {
|
||||||
return exec("bash -c \"free | awk '/Mem:/ {printf \\\"%.0f\\\", $3/$2 * 100}'\"")
|
const out = exec("bash -c \"nvidia-smi --query-gpu=utilization.gpu,temperature.gpu --format=csv,noheader,nounits 2>/dev/null\"")
|
||||||
|
const [usage, temp] = out.trim().split(", ")
|
||||||
|
return `GPU ${usage}% · ${temp}°C`
|
||||||
} catch {
|
} catch {
|
||||||
return "?"
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SystemStats() {
|
export default function SystemStats() {
|
||||||
const cpu = createPoll("0", 3000, () => getCpuUsage())
|
const cpu = createPoll("0", 3000, () => getCpuUsage())
|
||||||
const ram = createPoll("0", 5000, () => getRamUsage())
|
const ram = createPoll("", 5000, () => {
|
||||||
|
const r = getRamUsage()
|
||||||
|
return JSON.stringify(r)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ramLabel = ram((v: string) => {
|
||||||
|
try {
|
||||||
|
const r = JSON.parse(v)
|
||||||
|
return `RAM ${r.used}/${r.total}G`
|
||||||
|
} catch { return "RAM ?" }
|
||||||
|
})
|
||||||
|
|
||||||
|
const gpu = createPoll("", 5000, () => getGpuInfo())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box>
|
<box>
|
||||||
<label class="cpu module" label={cpu((v: string) => ` ${v}%`)} />
|
<label class="cpu module" label={cpu((v: string) => `CPU ${v}%`)} />
|
||||||
<label class="ram module" label={ram((v: string) => ` ${v}%`)} />
|
<label class="separator" label="│" />
|
||||||
|
<label class="ram module" label={ramLabel} />
|
||||||
|
<label class="separator" label="│" />
|
||||||
|
<label class="gpu module" label={gpu((v: string) => v || "GPU ?")} />
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import { createBinding } from "ags"
|
|||||||
export default function Volume() {
|
export default function Volume() {
|
||||||
const speaker = AstalWp.get_default()!.defaultSpeaker!
|
const speaker = AstalWp.get_default()!.defaultSpeaker!
|
||||||
|
|
||||||
const text = createBinding(speaker, "volume")((v: number) => {
|
const volume = createBinding(speaker, "volume")
|
||||||
|
const mute = createBinding(speaker, "mute")
|
||||||
|
|
||||||
|
const text = volume((v: number) => {
|
||||||
const pct = Math.round(v * 100)
|
const pct = Math.round(v * 100)
|
||||||
const muted = speaker.mute
|
const muted = speaker.mute
|
||||||
let icon = ""
|
let icon = ""
|
||||||
@@ -16,8 +19,8 @@ export default function Volume() {
|
|||||||
return `${icon} ${pct}%`
|
return `${icon} ${pct}%`
|
||||||
})
|
})
|
||||||
|
|
||||||
const cls = createBinding(speaker, "mute")((m: boolean) =>
|
const cls = mute((m: boolean) =>
|
||||||
m ? "volume muted" : "volume"
|
m ? "volume module muted" : "volume module"
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,32 +2,37 @@ import app from "ags/gtk3/app"
|
|||||||
import { Astal, Gtk, Gdk } from "ags/gtk3"
|
import { Astal, Gtk, Gdk } from "ags/gtk3"
|
||||||
import { createPoll } from "ags/time"
|
import { createPoll } from "ags/time"
|
||||||
import { getBrainState } from "../../lib/brain"
|
import { getBrainState } from "../../lib/brain"
|
||||||
|
import GLib from "gi://GLib"
|
||||||
|
|
||||||
function BrainContent() {
|
function BrainContent() {
|
||||||
const state = createPoll("", 10000, () => JSON.stringify(getBrainState()))
|
const state = createPoll("", 10000, () => JSON.stringify(getBrainState()))
|
||||||
|
|
||||||
|
const version = state((s: string) => {
|
||||||
|
try { return `kernel v${JSON.parse(s).kernelVersion}` } catch { return "" }
|
||||||
|
})
|
||||||
const focus = state((s: string) => {
|
const focus = state((s: string) => {
|
||||||
try { return JSON.parse(s).focus } catch { return "..." }
|
try { return JSON.parse(s).focus } catch { return "..." }
|
||||||
})
|
})
|
||||||
|
const session = state((s: string) => {
|
||||||
const todosLabel = state((s: string) => {
|
try { return JSON.parse(s).session || "aucune session active" } catch { return "..." }
|
||||||
|
})
|
||||||
|
const intentionsTitle = state((s: string) => {
|
||||||
try {
|
try {
|
||||||
const t = JSON.parse(s).todos
|
const i = JSON.parse(s).intentions
|
||||||
return `${t.open} ouverts / ${t.done} faits`
|
return ` INTENTIONS (${i.active} active${i.active > 1 ? "s" : ""})`
|
||||||
|
} catch { return " INTENTIONS" }
|
||||||
|
})
|
||||||
|
const intentionsList = state((s: string) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(s).intentions.names.map((n: string) => ` • ${n}`).join("\n") || " aucune"
|
||||||
} catch { return "..." }
|
} catch { return "..." }
|
||||||
})
|
})
|
||||||
|
const todosTitle = state((s: string) => {
|
||||||
|
try { const t = JSON.parse(s).todos; return `${t.open} ouverts / ${t.done} faits` } catch { return "" }
|
||||||
|
})
|
||||||
const todosList = state((s: string) => {
|
const todosList = state((s: string) => {
|
||||||
try {
|
try {
|
||||||
const t = JSON.parse(s).todos.top3
|
return JSON.parse(s).todos.top3.map((item: string) => ` ⬜ ${item}`).join("\n") || " rien"
|
||||||
return t.map((item: string) => ` ⬜ ${item}`).join("\n") || " rien"
|
|
||||||
} catch { return "..." }
|
|
||||||
})
|
|
||||||
|
|
||||||
const session = state((s: string) => {
|
|
||||||
try {
|
|
||||||
const sess = JSON.parse(s).session
|
|
||||||
return sess || "aucune"
|
|
||||||
} catch { return "..." }
|
} catch { return "..." }
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -35,30 +40,25 @@ function BrainContent() {
|
|||||||
<box orientation={Gtk.Orientation.VERTICAL} class="brain-content">
|
<box orientation={Gtk.Orientation.VERTICAL} class="brain-content">
|
||||||
<box class="brain-section">
|
<box class="brain-section">
|
||||||
<label class="brain-section-title" label=" FOCUS" xalign={0} />
|
<label class="brain-section-title" label=" FOCUS" xalign={0} />
|
||||||
|
<label class="brain-version" label={version} xalign={1} hexpand />
|
||||||
</box>
|
</box>
|
||||||
<label class="brain-focus" label={focus} xalign={0} wrap />
|
<label class="brain-focus" label={focus} xalign={0} wrap />
|
||||||
|
|
||||||
<box class="brain-divider" />
|
<box class="brain-divider" />
|
||||||
|
|
||||||
<box class="brain-section">
|
<box class="brain-section">
|
||||||
<label class="brain-section-title" label=" SESSION" xalign={0} />
|
<label class="brain-section-title" label=" SESSION" xalign={0} />
|
||||||
</box>
|
</box>
|
||||||
<label class="brain-session" label={session} xalign={0} />
|
<label class="brain-session" label={session} xalign={0} />
|
||||||
|
|
||||||
<box class="brain-divider" />
|
<box class="brain-divider" />
|
||||||
|
<box class="brain-section">
|
||||||
|
<label class="brain-section-title" label={intentionsTitle} xalign={0} />
|
||||||
|
</box>
|
||||||
|
<label class="brain-intentions-list" label={intentionsList} xalign={0} />
|
||||||
|
<box class="brain-divider" />
|
||||||
<box class="brain-section">
|
<box class="brain-section">
|
||||||
<label class="brain-section-title" label=" TODOS" xalign={0} />
|
<label class="brain-section-title" label=" TODOS" xalign={0} />
|
||||||
<label class="brain-todos-count" label={todosLabel} xalign={1} hexpand />
|
<label class="brain-todos-count" label={todosTitle} xalign={1} hexpand />
|
||||||
</box>
|
</box>
|
||||||
<label class="brain-todos-list" label={todosList} xalign={0} />
|
<label class="brain-todos-list" label={todosList} xalign={0} />
|
||||||
|
|
||||||
<box class="brain-divider" />
|
|
||||||
|
|
||||||
<box class="brain-section">
|
|
||||||
<label class="brain-section-title" label=" TERMINAL" xalign={0} />
|
|
||||||
</box>
|
|
||||||
<label class="brain-terminal-placeholder" label=" bientôt — agent brain-hud" xalign={0} />
|
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -76,12 +76,6 @@ export default function BrainPower(gdkmonitor: Gdk.Monitor) {
|
|||||||
anchor={TOP | LEFT | BOTTOM}
|
anchor={TOP | LEFT | BOTTOM}
|
||||||
application={app}
|
application={app}
|
||||||
layer={Astal.Layer.OVERLAY}
|
layer={Astal.Layer.OVERLAY}
|
||||||
>
|
|
||||||
<eventbox
|
|
||||||
onHoverLost={() => {
|
|
||||||
const win = app.get_window("brain-power")
|
|
||||||
if (win) win.visible = false
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<box orientation={Gtk.Orientation.VERTICAL} class="brain-panel">
|
<box orientation={Gtk.Orientation.VERTICAL} class="brain-panel">
|
||||||
<box class="brain-header">
|
<box class="brain-header">
|
||||||
@@ -98,7 +92,6 @@ export default function BrainPower(gdkmonitor: Gdk.Monitor) {
|
|||||||
</box>
|
</box>
|
||||||
<BrainContent />
|
<BrainContent />
|
||||||
</box>
|
</box>
|
||||||
</eventbox>
|
|
||||||
</window>
|
</window>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=Ghost Shell
|
Name=Ghost Shell
|
||||||
Comment=violet-chaton v2 AGS statusbar
|
Comment=violet-chaton v2 AGS statusbar
|
||||||
Exec=ags run -d /home/tetardtek/.config/ags-v3 -g 3
|
Exec=sh -c "ags run -d $HOME/.config/ags-v3 -g 3"
|
||||||
X-GNOME-Autostart-enabled=true
|
X-GNOME-Autostart-enabled=true
|
||||||
|
|||||||
Reference in New Issue
Block a user