brightctrl 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/index.js +430 -0
- package/package.json +45 -9
- package/index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# BrightCtrl
|
|
2
|
+
|
|
3
|
+
A lightweight DDC/CI monitor brightness controller for Linux — now a terminal UI built with [React](https://react.dev) / [Ink](https://github.com/vadimdemedes/ink) on [Bun](https://bun.sh).
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Auto-detects all DDC/CI-capable monitors
|
|
10
|
+
- Per-monitor brightness bars with live percentage
|
|
11
|
+
- Arrow key / vim (`h`/`l`) brightness adjustment (5% steps)
|
|
12
|
+
- **Sync mode** (`s`) — control all monitors at once
|
|
13
|
+
- Direct input dialog (`i`) — type a specific brightness value
|
|
14
|
+
- 500ms debounce on DDC writes (no flooding the i2c bus)
|
|
15
|
+
- Brightness-dependent color coding (red ≤20%, cyan 21-89%, yellow ≥90%)
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
### System
|
|
20
|
+
|
|
21
|
+
| Distro | Command |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Arch / Manjaro | `sudo pacman -S ddcutil` |
|
|
24
|
+
| Debian / Ubuntu | `sudo apt install ddcutil` |
|
|
25
|
+
| Fedora / RHEL | `sudo dnf install ddcutil` |
|
|
26
|
+
|
|
27
|
+
Then set up the i2c kernel module and add yourself to the i2c group:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
sudo usermod -aG i2c $USER
|
|
31
|
+
sudo modprobe i2c-dev
|
|
32
|
+
|
|
33
|
+
# Persist across reboots:
|
|
34
|
+
echo 'i2c-dev' | sudo tee /etc/modules-load.d/i2c.conf
|
|
35
|
+
|
|
36
|
+
# Log out and back in for the group change to take effect.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Verify everything works:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ddcutil detect
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Runtime
|
|
46
|
+
|
|
47
|
+
- [Node.js](https://nodejs.org) >= 18
|
|
48
|
+
- Or [Bun](https://bun.sh) 1.x (for development)
|
|
49
|
+
|
|
50
|
+
## Install & Run
|
|
51
|
+
|
|
52
|
+
### Quick — no install needed
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx brightctrl
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Global install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install -g brightctrl
|
|
62
|
+
brightctrl
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### From source
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/shahriyardx/brightctrl.git
|
|
69
|
+
cd brightctrl
|
|
70
|
+
bun install
|
|
71
|
+
bun src/index.tsx
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Controls
|
|
75
|
+
|
|
76
|
+
| Key | Action |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `↑` / `↓` | Select monitor |
|
|
79
|
+
| `←` / `h` | Decrease brightness 5% |
|
|
80
|
+
| `→` / `l` | Increase brightness 5% |
|
|
81
|
+
| `i` | Open input dialog (type 0–100, Enter to confirm) |
|
|
82
|
+
| `s` | Toggle sync mode (all monitors) |
|
|
83
|
+
| `r` | Refresh monitor detection |
|
|
84
|
+
| `q` | Quit |
|
|
85
|
+
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/index.tsx
|
|
5
|
+
import { render } from "ink";
|
|
6
|
+
|
|
7
|
+
// src/app.tsx
|
|
8
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
|
+
|
|
11
|
+
// src/ddcutil.ts
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
var execFileAsync = promisify(execFile);
|
|
15
|
+
function ddcutil(args, timeout = 8000) {
|
|
16
|
+
return execFileAsync("ddcutil", args, {
|
|
17
|
+
timeout,
|
|
18
|
+
encoding: "utf-8"
|
|
19
|
+
}).then((r) => r.stdout);
|
|
20
|
+
}
|
|
21
|
+
async function checkDdcutil() {
|
|
22
|
+
try {
|
|
23
|
+
await execFileAsync("which", ["ddcutil"]);
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function detectMonitors() {
|
|
30
|
+
const stdout = await ddcutil(["detect", "--brief"], 15000);
|
|
31
|
+
const monitors = [];
|
|
32
|
+
let current = null;
|
|
33
|
+
for (const line of stdout.split(`
|
|
34
|
+
`)) {
|
|
35
|
+
const t = line.trim();
|
|
36
|
+
if (t.startsWith("Display ")) {
|
|
37
|
+
if (current?.index != null)
|
|
38
|
+
monitors.push(current);
|
|
39
|
+
const m = t.match(/Display\s+(\d+)/);
|
|
40
|
+
current = {
|
|
41
|
+
index: m ? Number.parseInt(m[1], 10) : monitors.length + 1,
|
|
42
|
+
name: "Unknown Monitor",
|
|
43
|
+
bus: ""
|
|
44
|
+
};
|
|
45
|
+
} else if (t.includes("I2C bus:")) {
|
|
46
|
+
if (current)
|
|
47
|
+
current.bus = t.replace("I2C bus:", "").trim();
|
|
48
|
+
} else if (t.startsWith("Monitor:")) {
|
|
49
|
+
if (current) {
|
|
50
|
+
const p = t.replace("Monitor:", "").trim();
|
|
51
|
+
current.name = p.replace(/\s+unspecified/i, "").trim() || "Monitor";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (current?.index != null)
|
|
56
|
+
monitors.push(current);
|
|
57
|
+
return monitors;
|
|
58
|
+
}
|
|
59
|
+
async function getBrightness(displayIndex) {
|
|
60
|
+
try {
|
|
61
|
+
const stdout = await ddcutil(["getvcp", "10", `--display=${displayIndex}`], 5000);
|
|
62
|
+
const m = stdout.match(/current value\s*=\s*(\d+)/);
|
|
63
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function setBrightness(displayIndex, value) {
|
|
69
|
+
try {
|
|
70
|
+
await ddcutil(["setvcp", "10", String(Math.round(value)), `--display=${displayIndex}`], 5000);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/app.tsx
|
|
78
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
79
|
+
function BrightnessBar({
|
|
80
|
+
value,
|
|
81
|
+
width = 25
|
|
82
|
+
}) {
|
|
83
|
+
const filled = Math.round(value / 100 * width);
|
|
84
|
+
const empty = width - filled;
|
|
85
|
+
const color = value <= 20 ? "red" : value >= 90 ? "yellow" : "cyan";
|
|
86
|
+
return /* @__PURE__ */ jsxDEV(Box, {
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
89
|
+
color,
|
|
90
|
+
children: filled > 0 ? "█".repeat(filled) : ""
|
|
91
|
+
}, undefined, false, undefined, this),
|
|
92
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
93
|
+
color: "#444",
|
|
94
|
+
children: empty > 0 ? "░".repeat(empty) : ""
|
|
95
|
+
}, undefined, false, undefined, this),
|
|
96
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
97
|
+
children: " "
|
|
98
|
+
}, undefined, false, undefined, this),
|
|
99
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
100
|
+
color,
|
|
101
|
+
bold: true,
|
|
102
|
+
children: [
|
|
103
|
+
Math.round(value),
|
|
104
|
+
"%"
|
|
105
|
+
]
|
|
106
|
+
}, undefined, true, undefined, this)
|
|
107
|
+
]
|
|
108
|
+
}, undefined, true, undefined, this);
|
|
109
|
+
}
|
|
110
|
+
function MonitorCard({
|
|
111
|
+
monitor,
|
|
112
|
+
selected
|
|
113
|
+
}) {
|
|
114
|
+
return /* @__PURE__ */ jsxDEV(Box, {
|
|
115
|
+
borderStyle: selected ? "round" : "single",
|
|
116
|
+
borderColor: selected ? "cyan" : "gray",
|
|
117
|
+
flexDirection: "column",
|
|
118
|
+
paddingX: 1,
|
|
119
|
+
paddingY: 0,
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
122
|
+
gap: 1,
|
|
123
|
+
children: [
|
|
124
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
125
|
+
bold: true,
|
|
126
|
+
color: selected ? "cyan" : "white",
|
|
127
|
+
children: [
|
|
128
|
+
"Display ",
|
|
129
|
+
monitor.index
|
|
130
|
+
]
|
|
131
|
+
}, undefined, true, undefined, this),
|
|
132
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
133
|
+
color: "white",
|
|
134
|
+
children: monitor.name
|
|
135
|
+
}, undefined, false, undefined, this),
|
|
136
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
137
|
+
color: "gray",
|
|
138
|
+
children: monitor.bus
|
|
139
|
+
}, undefined, false, undefined, this),
|
|
140
|
+
selected ? /* @__PURE__ */ jsxDEV(Text, {
|
|
141
|
+
color: "cyan",
|
|
142
|
+
children: "◄"
|
|
143
|
+
}, undefined, false, undefined, this) : null
|
|
144
|
+
]
|
|
145
|
+
}, undefined, true, undefined, this),
|
|
146
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
147
|
+
children: /* @__PURE__ */ jsxDEV(BrightnessBar, {
|
|
148
|
+
value: monitor.brightness
|
|
149
|
+
}, undefined, false, undefined, this)
|
|
150
|
+
}, undefined, false, undefined, this)
|
|
151
|
+
]
|
|
152
|
+
}, undefined, true, undefined, this);
|
|
153
|
+
}
|
|
154
|
+
function ErrorPanel() {
|
|
155
|
+
return /* @__PURE__ */ jsxDEV(Box, {
|
|
156
|
+
flexDirection: "column",
|
|
157
|
+
marginTop: 1,
|
|
158
|
+
children: [
|
|
159
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
160
|
+
color: "yellow",
|
|
161
|
+
children: "Troubleshooting:"
|
|
162
|
+
}, undefined, false, undefined, this),
|
|
163
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
164
|
+
color: "gray",
|
|
165
|
+
children: " sudo pacman -S ddcutil"
|
|
166
|
+
}, undefined, false, undefined, this),
|
|
167
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
168
|
+
color: "gray",
|
|
169
|
+
children: " sudo usermod -aG i2c $USER"
|
|
170
|
+
}, undefined, false, undefined, this),
|
|
171
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
172
|
+
color: "gray",
|
|
173
|
+
children: " sudo modprobe i2c-dev"
|
|
174
|
+
}, undefined, false, undefined, this),
|
|
175
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
176
|
+
color: "gray",
|
|
177
|
+
children: "echo 'i2c-dev' | sudo tee /etc/modules-load.d/i2c.conf"
|
|
178
|
+
}, undefined, false, undefined, this),
|
|
179
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
180
|
+
color: "gray",
|
|
181
|
+
children: " (log out and in for group change)"
|
|
182
|
+
}, undefined, false, undefined, this)
|
|
183
|
+
]
|
|
184
|
+
}, undefined, true, undefined, this);
|
|
185
|
+
}
|
|
186
|
+
function App() {
|
|
187
|
+
const { exit } = useApp();
|
|
188
|
+
const [monitors, setMonitors] = useState([]);
|
|
189
|
+
const [selected, setSelected] = useState(0);
|
|
190
|
+
const [syncMode, setSyncMode] = useState(false);
|
|
191
|
+
const [status, setStatus] = useState("Starting...");
|
|
192
|
+
const [error, setError] = useState(null);
|
|
193
|
+
const [loading, setLoading] = useState(true);
|
|
194
|
+
const [typing, setTyping] = useState(false);
|
|
195
|
+
const [typed, setTyped] = useState("");
|
|
196
|
+
const debounceTimer = useRef(null);
|
|
197
|
+
const pendingRef = useRef(new Map);
|
|
198
|
+
function scheduleBrightness(index, value) {
|
|
199
|
+
pendingRef.current.set(index, value);
|
|
200
|
+
if (debounceTimer.current)
|
|
201
|
+
clearTimeout(debounceTimer.current);
|
|
202
|
+
debounceTimer.current = setTimeout(() => {
|
|
203
|
+
const vals = new Map(pendingRef.current);
|
|
204
|
+
pendingRef.current.clear();
|
|
205
|
+
for (const [idx, v] of vals) {
|
|
206
|
+
setBrightness(idx, v);
|
|
207
|
+
}
|
|
208
|
+
}, 500);
|
|
209
|
+
}
|
|
210
|
+
const load = useCallback(async () => {
|
|
211
|
+
setLoading(true);
|
|
212
|
+
setError(null);
|
|
213
|
+
setStatus("Checking ddcutil...");
|
|
214
|
+
const hasDdcutil = await checkDdcutil();
|
|
215
|
+
if (!hasDdcutil) {
|
|
216
|
+
setError("ddcutil not found. Install: sudo pacman -S ddcutil");
|
|
217
|
+
setStatus("ddcutil missing");
|
|
218
|
+
setLoading(false);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
setStatus("Detecting monitors...");
|
|
222
|
+
const infoList = await detectMonitors();
|
|
223
|
+
if (infoList.length === 0) {
|
|
224
|
+
setError("No DDC/CI monitors detected");
|
|
225
|
+
setStatus("No monitors found");
|
|
226
|
+
setLoading(false);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
setStatus(`Reading brightness for ${infoList.length} monitor(s)...`);
|
|
230
|
+
const loaded = [];
|
|
231
|
+
for (const info of infoList) {
|
|
232
|
+
const b = await getBrightness(info.index);
|
|
233
|
+
loaded.push({ ...info, brightness: b ?? 50 });
|
|
234
|
+
}
|
|
235
|
+
setMonitors(loaded);
|
|
236
|
+
setSelected((s) => Math.min(s, loaded.length - 1));
|
|
237
|
+
setStatus(`${loaded.length} monitor(s) — DDC/CI via ddcutil`);
|
|
238
|
+
setLoading(false);
|
|
239
|
+
}, []);
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
load();
|
|
242
|
+
}, [load]);
|
|
243
|
+
function adjustBrightness(delta) {
|
|
244
|
+
if (monitors.length === 0)
|
|
245
|
+
return;
|
|
246
|
+
const idx = selected;
|
|
247
|
+
setMonitors((prev) => prev.map((m, i) => {
|
|
248
|
+
if (!syncMode && i !== idx)
|
|
249
|
+
return m;
|
|
250
|
+
const v = Math.max(0, Math.min(100, m.brightness + delta));
|
|
251
|
+
scheduleBrightness(m.index, v);
|
|
252
|
+
return { ...m, brightness: v };
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
useInput((input, key) => {
|
|
256
|
+
if (typing) {
|
|
257
|
+
if (key.escape) {
|
|
258
|
+
setTyping(false);
|
|
259
|
+
setTyped("");
|
|
260
|
+
} else if (key.return) {
|
|
261
|
+
const v = Number.parseInt(typed, 10);
|
|
262
|
+
if (!Number.isNaN(v) && v >= 0 && v <= 100) {
|
|
263
|
+
setMonitors((prev) => prev.map((m, i) => {
|
|
264
|
+
if (!syncMode && i !== selected)
|
|
265
|
+
return m;
|
|
266
|
+
scheduleBrightness(m.index, v);
|
|
267
|
+
return { ...m, brightness: v };
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
setTyping(false);
|
|
271
|
+
setTyped("");
|
|
272
|
+
} else if (key.backspace) {
|
|
273
|
+
setTyped((p) => p.slice(0, -1));
|
|
274
|
+
} else if (/^[0-9]$/.test(input)) {
|
|
275
|
+
setTyped((p) => (p + input).slice(0, 3));
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (input === "q")
|
|
280
|
+
exit();
|
|
281
|
+
if (key.upArrow) {
|
|
282
|
+
setSelected((s) => Math.max(0, s - 1));
|
|
283
|
+
} else if (key.downArrow) {
|
|
284
|
+
setSelected((s) => Math.min(monitors.length - 1, s + 1));
|
|
285
|
+
} else if (input === "h" || key.leftArrow) {
|
|
286
|
+
adjustBrightness(-5);
|
|
287
|
+
} else if (input === "l" || key.rightArrow) {
|
|
288
|
+
adjustBrightness(5);
|
|
289
|
+
} else if (input === "s") {
|
|
290
|
+
setSyncMode((s) => !s);
|
|
291
|
+
} else if (input === "r") {
|
|
292
|
+
load();
|
|
293
|
+
} else if (input === "i") {
|
|
294
|
+
setTyping(true);
|
|
295
|
+
setTyped("");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
return /* @__PURE__ */ jsxDEV(Box, {
|
|
299
|
+
flexDirection: "column",
|
|
300
|
+
padding: 1,
|
|
301
|
+
children: [
|
|
302
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
303
|
+
gap: 2,
|
|
304
|
+
children: [
|
|
305
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
306
|
+
bold: true,
|
|
307
|
+
color: "cyan",
|
|
308
|
+
children: "BrightCtrl"
|
|
309
|
+
}, undefined, false, undefined, this),
|
|
310
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
311
|
+
color: "gray",
|
|
312
|
+
children: "DDC/CI Brightness Controller"
|
|
313
|
+
}, undefined, false, undefined, this)
|
|
314
|
+
]
|
|
315
|
+
}, undefined, true, undefined, this),
|
|
316
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
317
|
+
color: "#333",
|
|
318
|
+
children: "─".repeat(56)
|
|
319
|
+
}, undefined, false, undefined, this),
|
|
320
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
321
|
+
marginBottom: 1,
|
|
322
|
+
gap: 3,
|
|
323
|
+
children: [
|
|
324
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
325
|
+
color: syncMode ? "green" : "gray",
|
|
326
|
+
children: [
|
|
327
|
+
"[",
|
|
328
|
+
syncMode ? "✓" : " ",
|
|
329
|
+
"] Sync All ",
|
|
330
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
331
|
+
color: "gray",
|
|
332
|
+
children: "(s)"
|
|
333
|
+
}, undefined, false, undefined, this)
|
|
334
|
+
]
|
|
335
|
+
}, undefined, true, undefined, this),
|
|
336
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
337
|
+
children: [
|
|
338
|
+
"⟳ Refresh ",
|
|
339
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
340
|
+
color: "gray",
|
|
341
|
+
children: "(r)"
|
|
342
|
+
}, undefined, false, undefined, this)
|
|
343
|
+
]
|
|
344
|
+
}, undefined, true, undefined, this),
|
|
345
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
346
|
+
children: [
|
|
347
|
+
"✕ Quit ",
|
|
348
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
349
|
+
color: "gray",
|
|
350
|
+
children: "(q)"
|
|
351
|
+
}, undefined, false, undefined, this)
|
|
352
|
+
]
|
|
353
|
+
}, undefined, true, undefined, this)
|
|
354
|
+
]
|
|
355
|
+
}, undefined, true, undefined, this),
|
|
356
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
357
|
+
marginBottom: 1,
|
|
358
|
+
children: loading ? /* @__PURE__ */ jsxDEV(Text, {
|
|
359
|
+
color: "gray",
|
|
360
|
+
children: [
|
|
361
|
+
status,
|
|
362
|
+
"..."
|
|
363
|
+
]
|
|
364
|
+
}, undefined, true, undefined, this) : error ? /* @__PURE__ */ jsxDEV(Text, {
|
|
365
|
+
color: "red",
|
|
366
|
+
children: error
|
|
367
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
|
|
368
|
+
color: "gray",
|
|
369
|
+
children: status
|
|
370
|
+
}, undefined, false, undefined, this)
|
|
371
|
+
}, undefined, false, undefined, this),
|
|
372
|
+
!loading && !error && monitors.length > 0 ? /* @__PURE__ */ jsxDEV(Box, {
|
|
373
|
+
flexDirection: "column",
|
|
374
|
+
gap: 0,
|
|
375
|
+
children: monitors.map((m, i) => /* @__PURE__ */ jsxDEV(MonitorCard, {
|
|
376
|
+
monitor: m,
|
|
377
|
+
selected: i === selected
|
|
378
|
+
}, m.index, false, undefined, this))
|
|
379
|
+
}, undefined, false, undefined, this) : null,
|
|
380
|
+
!loading && error ? /* @__PURE__ */ jsxDEV(ErrorPanel, {}, undefined, false, undefined, this) : null,
|
|
381
|
+
typing ? /* @__PURE__ */ jsxDEV(Box, {
|
|
382
|
+
flexDirection: "column",
|
|
383
|
+
marginTop: 1,
|
|
384
|
+
marginBottom: 1,
|
|
385
|
+
children: /* @__PURE__ */ jsxDEV(Box, {
|
|
386
|
+
borderStyle: "round",
|
|
387
|
+
borderColor: "cyan",
|
|
388
|
+
paddingX: 1,
|
|
389
|
+
flexDirection: "column",
|
|
390
|
+
children: [
|
|
391
|
+
/* @__PURE__ */ jsxDEV(Box, {
|
|
392
|
+
gap: 1,
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
395
|
+
bold: true,
|
|
396
|
+
color: "cyan",
|
|
397
|
+
children: "Brightness:"
|
|
398
|
+
}, undefined, false, undefined, this),
|
|
399
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
400
|
+
bold: true,
|
|
401
|
+
color: "white",
|
|
402
|
+
children: [
|
|
403
|
+
typed || "0",
|
|
404
|
+
"_"
|
|
405
|
+
]
|
|
406
|
+
}, undefined, true, undefined, this)
|
|
407
|
+
]
|
|
408
|
+
}, undefined, true, undefined, this),
|
|
409
|
+
/* @__PURE__ */ jsxDEV(Text, {
|
|
410
|
+
color: "gray",
|
|
411
|
+
children: "Enter 0-100 ↵ confirm ⎋ cancel"
|
|
412
|
+
}, undefined, false, undefined, this)
|
|
413
|
+
]
|
|
414
|
+
}, undefined, true, undefined, this)
|
|
415
|
+
}, undefined, false, undefined, this) : null,
|
|
416
|
+
!loading ? /* @__PURE__ */ jsxDEV(Box, {
|
|
417
|
+
marginTop: 1,
|
|
418
|
+
children: error ? null : /* @__PURE__ */ jsxDEV(Text, {
|
|
419
|
+
color: "gray",
|
|
420
|
+
children: "↑↓ select h/l ←→ brightness i input s sync r refresh q quit"
|
|
421
|
+
}, undefined, false, undefined, this)
|
|
422
|
+
}, undefined, false, undefined, this) : null
|
|
423
|
+
]
|
|
424
|
+
}, undefined, true, undefined, this);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/index.tsx
|
|
428
|
+
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
429
|
+
var { waitUntilExit } = render(/* @__PURE__ */ jsxDEV2(App, {}, undefined, false, undefined, this));
|
|
430
|
+
await waitUntilExit();
|
package/package.json
CHANGED
|
@@ -1,11 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brightctrl",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Terminal UI for DDC/CI monitor brightness control",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"brightctrl": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "bun build src/index.tsx --outdir dist --target node --format esm --external ink --external react --external react-reconciler --external scheduler --external '@types/react' && sed -i 's_^#!/usr/bin/env bun_#!/usr/bin/env node_' dist/index.js",
|
|
18
|
+
"prepublishOnly": "bun run build",
|
|
19
|
+
"lint": "biome check src/",
|
|
20
|
+
"format": "biome check --write src/",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"ink": "^5.2.0",
|
|
25
|
+
"react": "^18.3.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@biomejs/biome": "^2.4.15",
|
|
29
|
+
"@types/react": "^18.3.0",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/shahriyardx/brightctrl.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/shahriyardx/brightctrl",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"brightness",
|
|
39
|
+
"ddc-ci",
|
|
40
|
+
"ddcutil",
|
|
41
|
+
"monitor",
|
|
42
|
+
"tui",
|
|
43
|
+
"ink",
|
|
44
|
+
"waybar"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT"
|
|
47
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Placeholder
|