feat: add readme and clean up comments and code quality. Also update metadata so that it reads better in settings

This commit is contained in:
Lucas Oskorep
2026-06-29 21:35:13 -04:00
parent 2c0a09efb0
commit 408288f79e
9 changed files with 58 additions and 38 deletions
+47
View File
@@ -0,0 +1,47 @@
# DDC Brightness Control
This project aims to be a simple and easy to use DDC brightness control plugin for Gnome
![img.png](attachments/panel.png)
## Features
- Brightness control via panel
- Adjustable keybindings for brightness controls
- Active screen detection
- By default, keybinding is alt+shift+f1/f2 for down/up on the active display
- Display linking if you want to adjust all displays at once
## Planned Features?
- Would be nice to get this added to the system status menu at some point isntead of its own dot
- Support for direct i2c or better ddc controllers (not ddcutil)
- You tell me
## Installation
(Examples use arch, but all packages should be available on other distros as well)
Be sure to have [ddcutil](https://www.ddcutil.com/) installed it is a hard requirement for now
Also, if installing from source install just which is the chosen command runner for this project
```bash
sudo pacman -Syu ddcutil just
```
Add your user to the i2c group so it can use i2c without root and reboot/login+out
```bash
sudo usermod -aG i2c $USER
reboot
```
Now install the extension from version control
(until we get to the gnome extension store)
```bash
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+3
View File
@@ -38,5 +38,8 @@ live-debug:
lint: lint:
pnpm run lint pnpm run lint
lint-fix:
pnpm run lint --fix
clean: clean:
pnpm run clean pnpm run clean
+1 -4
View File
@@ -1,12 +1,9 @@
{ {
"name": "ddc-brightness-control", "name": "DDC Brightness Controller",
"description": "Control display brightness via DDC/CI using ddcutil. Bind keyboard shortcuts or use the panel menu to adjust monitor brightness.", "description": "Control display brightness via DDC/CI using ddcutil. Bind keyboard shortcuts or use the panel menu to adjust monitor brightness.",
"uuid": "ddcbrightness@lucaso.io", "uuid": "ddcbrightness@lucaso.io",
"settings-schema": "org.gnome.shell.extensions.ddcbrightness", "settings-schema": "org.gnome.shell.extensions.ddcbrightness",
"shell-version": [ "shell-version": [
"47",
"48",
"49",
"50" "50"
] ]
} }
-4
View File
@@ -4,10 +4,6 @@ export const SCHEMA_ID = 'org.gnome.shell.extensions.ddcbrightness';
/** VCP feature code used when none is configured (10 = brightness). */ /** VCP feature code used when none is configured (10 = brightness). */
export const DEFAULT_VCP_CODE = '10'; export const DEFAULT_VCP_CODE = '10';
/**
* Scales the mandatory I2C delays ddcutil inserts around DDC/CI transactions.
* Lower = faster but less tolerant of flaky monitors. Tune per hardware.
*/
export const DDCUTIL_SLEEP_MULTIPLIER = '0.5'; export const DDCUTIL_SLEEP_MULTIPLIER = '0.5';
/** Prefix for all console logging from this extension. */ /** Prefix for all console logging from this extension. */
+5 -18
View File
@@ -1,10 +1,9 @@
import Gio from 'gi://Gio'; import Gio from 'gi://Gio';
import St from 'gi://St'; import St from 'gi://St';
import * as Slider from 'resource:///org/gnome/shell/ui/slider.js'; import * as Slider from 'resource:///org/gnome/shell/ui/slider.js';
import { DEFAULT_VCP_CODE, LOG_PREFIX } from './constants.js'; import {DEFAULT_VCP_CODE, LOG_PREFIX} from './constants.js';
import * as Ddcutil from './ddcutil.js'; import * as Ddcutil from './ddcutil.js';
/** A detected display plus the UI refs and runtime state attached to it. */
export interface DisplayInfo { export interface DisplayInfo {
bus: string; bus: string;
name: string; name: string;
@@ -13,30 +12,18 @@ export interface DisplayInfo {
currentValue: number; currentValue: number;
slider: Slider.Slider | null; slider: Slider.Slider | null;
valueLabel: St.Label | null; valueLabel: St.Label | null;
// Reconciliation state for writes: `sentValue` is the value of the most
// recent write handed to ddcutil; `inFlight` is true while that write runs.
sentValue: number; sentValue: number;
inFlight: boolean; inFlight: boolean;
reading: boolean; reading: boolean;
updatingFromCode: boolean; updatingFromCode: boolean;
} }
/**
* Owns the set of detected displays and all brightness operations on them
* (detection, monitor mapping, reads, and writes via ddcutil).
*
* Writes use a reconciliation model rather than debouncing: `currentValue` is
* the user's target and at most one write per display is ever in flight. When a
* write finishes, if the target has since moved we immediately send the latest
* value — so intermediate values are coalesced and we never queue stale writes.
*/
export class DisplayController { export class DisplayController {
private _settings: Gio.Settings; private _settings: Gio.Settings;
private _displays: DisplayInfo[] = []; private _displays: DisplayInfo[] = [];
private _detectComplete = false; private _detectComplete = false;
private _disposed = false; private _disposed = false;
/** Invoked after detection finishes so the UI can rebuild itself. */
onDetectComplete: (() => void) | null = null; onDetectComplete: (() => void) | null = null;
constructor(settings: Gio.Settings) { constructor(settings: Gio.Settings) {
@@ -55,18 +42,15 @@ export class DisplayController {
return this._settings.get_string('vcp-code') || DEFAULT_VCP_CODE; return this._settings.get_string('vcp-code') || DEFAULT_VCP_CODE;
} }
/** Clear all state back to the pre-detection condition. */
reset(): void { reset(): void {
this._displays = []; this._displays = [];
this._detectComplete = false; this._detectComplete = false;
} }
/** Stop issuing further writes. Call before disposal. */
cleanup(): void { cleanup(): void {
this._disposed = true; this._disposed = true;
} }
/** Detect displays, map them to monitors, then read their brightness. */
detect(): void { detect(): void {
Ddcutil.detectDisplays((parsed) => { Ddcutil.detectDisplays((parsed) => {
this._displays = parsed.map((p) => ({ this._displays = parsed.map((p) => ({
@@ -117,7 +101,10 @@ export class DisplayController {
return global.display.get_primary_monitor(); return global.display.get_primary_monitor();
} }
/** The display to act on for global actions, honoring "link displays". */ /**
* The display to act on for global actions, honoring "link displays".
* This should grab the display that the active window is part of for adjusting when not linked
*/
getActiveDisplay(): DisplayInfo | null { getActiveDisplay(): DisplayInfo | null {
if (this._settings.get_boolean('link-displays')) { if (this._settings.get_boolean('link-displays')) {
return this._displays[0] ?? null; return this._displays[0] ?? null;
-1
View File
@@ -42,7 +42,6 @@ export class BrightnessIndicator {
this.rebuildMenu(); this.rebuildMenu();
} }
/** The underlying panel button, for adding to the status area. */
get button(): PanelMenu.Button { get button(): PanelMenu.Button {
return this._button; return this._button;
} }
+2 -7
View File
@@ -3,16 +3,11 @@ import Meta from 'gi://Meta';
import Shell from 'gi://Shell'; import Shell from 'gi://Shell';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
/** Map of settings key -> callback to run when the shortcut fires. */
export type KeybindingActions = Record<string, () => void>; export type KeybindingActions = Record<string, () => void>;
/**
* Registers the extension's keyboard shortcuts with the window manager and
* keeps them in sync as their settings change.
*/
export class KeybindingManager { export class KeybindingManager {
private _settings: Gio.Settings; private readonly _settings: Gio.Settings;
private _actions: KeybindingActions; private readonly _actions: KeybindingActions;
private _bindings: Map<string, number> = new Map(); private _bindings: Map<string, number> = new Map();
constructor(settings: Gio.Settings, actions: KeybindingActions) { constructor(settings: Gio.Settings, actions: KeybindingActions) {
-4
View File
@@ -1,8 +1,5 @@
/* DDC Brightness Control styling */
.ddc-brightness-slider-box { .ddc-brightness-slider-box {
padding: 8px 12px; padding: 8px 12px;
spacing: 8px;
} }
.ddc-brightness-value-label { .ddc-brightness-value-label {
@@ -13,7 +10,6 @@
text-align: right; text-align: right;
} }
/* Custom slider */
.slider { .slider {
height: 24px; height: 24px;
border-radius: 12px; border-radius: 12px;