diff --git a/assets/icons/hicolor/scalable/devices/fa-brightness-symbolic.svg b/assets/icons/hicolor/scalable/devices/fa-brightness-symbolic.svg
new file mode 100644
index 0000000..9d48459
--- /dev/null
+++ b/assets/icons/hicolor/scalable/devices/fa-brightness-symbolic.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/hicolor/scalable/devices/fa-keyboard-symbolic.svg b/assets/icons/hicolor/scalable/devices/fa-keyboard-symbolic.svg
new file mode 100644
index 0000000..ef73326
--- /dev/null
+++ b/assets/icons/hicolor/scalable/devices/fa-keyboard-symbolic.svg
@@ -0,0 +1 @@
+
diff --git a/utils/brightness.ts b/utils/brightness.ts
new file mode 100644
index 0000000..55dc3b3
--- /dev/null
+++ b/utils/brightness.ts
@@ -0,0 +1,142 @@
+import GObject, { register, getter, setter } from "ags/gobject";
+import { monitorFile, readFileAsync } from "ags/file";
+import { exec, execAsync } from "ags/process";
+import { onCleanup } from "ags";
+
+const get = (args: string) => Number(exec(`brightnessctl ${args}`));
+const screen = exec(`bash -c "ls -w1 /sys/class/backlight | head -1"`);
+const kbd = exec(`bash -c "ls -w1 /sys/class/leds | head -1"`);
+
+@register({ GTypeName: "Brightness" })
+export default class Brightness extends GObject.Object {
+ // @ts-ignore - notify method is provided by GObject at runtime
+ notify(property: string): void;
+ static instance: Brightness;
+ static get_default() {
+ if (!this.instance) this.instance = new Brightness();
+ return this.instance;
+ }
+
+ #hasBacklight = false;
+ #kbdMax = 0;
+ #kbd = 0;
+ #screenMax = 0;
+ #screen = 0;
+ #screenMonitor: any = null;
+ #kbdMonitor: any = null;
+ #settingScreen = false;
+ #settingKbd = false;
+
+ constructor() {
+ super();
+ this.initializeHardware();
+ }
+
+ private initializeHardware(): void {
+ this.#hasBacklight = exec(`bash -c "ls /sys/class/backlight"`).length > 0;
+ if (!this.#hasBacklight) return;
+
+ try {
+ this.#kbdMax = get(`--device ${kbd} max`);
+ this.#kbd = get(`--device ${kbd} get`);
+ this.#screenMax = get("max");
+ this.#screen = get("get") / this.#screenMax;
+
+ this.#screenMonitor = monitorFile(
+ `/sys/class/backlight/${screen}/brightness`,
+ async (f) => {
+ if (this.#settingScreen) return;
+
+ try {
+ const v = await readFileAsync(f);
+ const newValue = Number(v) / this.#screenMax;
+ if (Math.abs(this.#screen - newValue) > 0.001) {
+ this.#screen = newValue;
+ this.notify("screen");
+ }
+ } catch (error) {
+ console.error("Error reading screen brightness:", error);
+ }
+ },
+ );
+
+ this.#kbdMonitor = monitorFile(
+ `/sys/class/leds/${kbd}/brightness`,
+ async (f) => {
+ if (this.#settingKbd) return;
+
+ try {
+ const v = await readFileAsync(f);
+ const newValue = Number(v);
+ if (this.#kbd !== newValue) {
+ this.#kbd = newValue;
+ this.notify("kbd");
+ }
+ } catch (error) {
+ console.error("Error reading keyboard brightness:", error);
+ }
+ },
+ );
+
+ onCleanup(() => {
+ this.#screenMonitor?.cancel?.();
+ this.#kbdMonitor?.cancel?.();
+ });
+ } catch (error) {
+ console.error("Error initializing brightness controls:", error);
+ this.#hasBacklight = false;
+ }
+ }
+
+ @getter(Boolean)
+ get hasBacklight(): boolean {
+ return this.#hasBacklight;
+ }
+
+ @getter(Number)
+ get kbd(): number {
+ return this.#kbd;
+ }
+
+ @setter(Number)
+ set kbd(value: number) {
+ if (!this.#hasBacklight || value < 0 || value > this.#kbdMax) return;
+
+ this.#settingKbd = true;
+ execAsync(`brightnessctl -d ${kbd} s ${value} -q`)
+ .then(() => {
+ setTimeout(() => (this.#settingKbd = false), 100);
+ })
+ .catch((error) => {
+ console.error("Error setting keyboard brightness:", error);
+ this.#settingKbd = false;
+ });
+ }
+
+ @getter(Number)
+ get screen(): number {
+ return this.#screen;
+ }
+
+ @setter(Number)
+ set screen(percent: number) {
+ if (!this.#hasBacklight) return;
+
+ percent = Math.max(0.01, Math.min(1, percent)); // Minimum 1% to avoid complete darkness
+
+ this.#screen = percent;
+ this.notify("screen");
+
+ this.#settingScreen = true;
+ execAsync(`brightnessctl -d ${screen} set ${Math.floor(percent * 100)}% -q`)
+ .then(() => {
+ setTimeout(() => (this.#settingScreen = false), 200);
+ })
+ .catch((error) => {
+ console.error("Error setting screen brightness:", error);
+ this.#screen = get("get") / this.#screenMax;
+ this.notify("screen");
+ this.#settingScreen = false;
+ });
+ }
+}
diff --git a/widgets/control-center/sliding-controls.tsx b/widgets/control-center/sliding-controls.tsx
index b90d561..a836605 100644
--- a/widgets/control-center/sliding-controls.tsx
+++ b/widgets/control-center/sliding-controls.tsx
@@ -1,12 +1,14 @@
import { createBinding } from "ags";
import { Gdk, Gtk } from "ags/gtk4";
import AstalWp from "gi://AstalWp";
+import Brightness from "../../utils/brightness";
export const SlidingControls = () => {
const { VERTICAL } = Gtk.Orientation;
const { defaultSpeaker: speaker, defaultMicrophone: microphone } =
AstalWp.get_default()!;
+ const brightness = Brightness.get_default();
const speakerIsMuted = createBinding(speaker, "mute");
@@ -36,6 +38,33 @@ export const SlidingControls = () => {
cursor={Gdk.Cursor.new_from_name("pointer", null)}
/>
+
+
+
+ {
+ brightness.screen = self.value;
+ }}
+ value={createBinding(brightness, "screen")}
+ cursor={Gdk.Cursor.new_from_name("pointer", null)}
+ />
+
+
+
+
+ {
+ brightness.kbd = self.value;
+ }}
+ value={createBinding(brightness, "kbd")}
+ cursor={Gdk.Cursor.new_from_name("pointer", null)}
+ />
+
);
};