candela-ctrl 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jon Blanchard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: candela-ctrl
3
+ Version: 0.1.0
4
+ Summary: Manual brightness and color temperature control for Linux
5
+ Author: Jon Blanchard
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jfblanchard/candela
8
+ Project-URL: Repository, https://github.com/jfblanchard/candela
9
+ Keywords: brightness,monitor,color-temperature,linux,night-mode,redshift,display
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: X11 Applications :: Qt
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Desktop Environment
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: PyQt6
21
+ Dynamic: license-file
22
+
23
+ # Candela
24
+
25
+ > Manual brightness and color temperature control for Linux — named after the SI unit of luminous intensity.
26
+
27
+ A clean, dark-themed desktop utility for tuning your monitor at night (or any time). Two sliders: one for brightness, one for color temperature in Kelvin. No automatic scheduling, no daemons — just instant manual control when you want it.
28
+
29
+ ![screenshot placeholder](screenshot.png)
30
+
31
+ ## Why Candela?
32
+
33
+ Most Linux tools in this space are either time-based and automatic (Redshift, Gammastep), command-line only (`xrandr`, `sct`), or have dated UIs that expose raw R/G/B gamma channels instead of a human-friendly Kelvin temperature slider. Candela aims to be the tool you reach for when you just want to quickly dim your screen and warm the color for late-night use.
34
+
35
+ ## Features
36
+
37
+ - **Brightness slider** — 10% to 100%
38
+ - **Color temperature slider** — 2000K (warm candlelight) to 6500K (daylight)
39
+ - **Multi-monitor support** — dropdown auto-populated from connected outputs
40
+ - **Instant apply** — changes take effect as you move the slider
41
+ - **Settings persist** after closing, within your X session (until logout/reboot)
42
+ - **Reset to Defaults** button restores 6500K / 100% brightness
43
+ - Dark UI — easy on the eyes when you need it most
44
+
45
+ ## Requirements
46
+
47
+ **Python:** 3.10+
48
+
49
+ **Python packages:**
50
+ ```bash
51
+ pip install PyQt6
52
+ ```
53
+
54
+ **System packages:**
55
+ ```bash
56
+ sudo apt install redshift # Debian / Ubuntu / Mint
57
+ sudo dnf install redshift # Fedora
58
+ sudo pacman -S redshift # Arch / Manjaro
59
+ ```
60
+ `redshift` handles color temperature. Brightness works without it (software gamma via `xrandr`).
61
+
62
+ > **Note:** X11 only. Wayland is not currently supported.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ git clone https://github.com/jfblanchard/candela.git
68
+ cd candela
69
+ pip install PyQt6
70
+ python candela.py
71
+ ```
72
+
73
+ ## Install via pip *(coming soon)*
74
+
75
+ ```bash
76
+ pip install candela-ctrl
77
+ candela
78
+ ```
79
+
80
+ ## Build a standalone binary
81
+
82
+ ```bash
83
+ pip install pyinstaller
84
+ pyinstaller --onefile --windowed --name candela candela.py
85
+ # Produces: dist/candela — single executable, no Python required
86
+ ```
87
+
88
+ ## Contributing
89
+
90
+ Issues and PRs welcome. If your monitor supports DDC/CI, hardware brightness control (via `ddcutil`) is the next planned feature — real backlight reduction instead of software gamma.
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,72 @@
1
+ # Candela
2
+
3
+ > Manual brightness and color temperature control for Linux — named after the SI unit of luminous intensity.
4
+
5
+ A clean, dark-themed desktop utility for tuning your monitor at night (or any time). Two sliders: one for brightness, one for color temperature in Kelvin. No automatic scheduling, no daemons — just instant manual control when you want it.
6
+
7
+ ![screenshot placeholder](screenshot.png)
8
+
9
+ ## Why Candela?
10
+
11
+ Most Linux tools in this space are either time-based and automatic (Redshift, Gammastep), command-line only (`xrandr`, `sct`), or have dated UIs that expose raw R/G/B gamma channels instead of a human-friendly Kelvin temperature slider. Candela aims to be the tool you reach for when you just want to quickly dim your screen and warm the color for late-night use.
12
+
13
+ ## Features
14
+
15
+ - **Brightness slider** — 10% to 100%
16
+ - **Color temperature slider** — 2000K (warm candlelight) to 6500K (daylight)
17
+ - **Multi-monitor support** — dropdown auto-populated from connected outputs
18
+ - **Instant apply** — changes take effect as you move the slider
19
+ - **Settings persist** after closing, within your X session (until logout/reboot)
20
+ - **Reset to Defaults** button restores 6500K / 100% brightness
21
+ - Dark UI — easy on the eyes when you need it most
22
+
23
+ ## Requirements
24
+
25
+ **Python:** 3.10+
26
+
27
+ **Python packages:**
28
+ ```bash
29
+ pip install PyQt6
30
+ ```
31
+
32
+ **System packages:**
33
+ ```bash
34
+ sudo apt install redshift # Debian / Ubuntu / Mint
35
+ sudo dnf install redshift # Fedora
36
+ sudo pacman -S redshift # Arch / Manjaro
37
+ ```
38
+ `redshift` handles color temperature. Brightness works without it (software gamma via `xrandr`).
39
+
40
+ > **Note:** X11 only. Wayland is not currently supported.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ git clone https://github.com/jfblanchard/candela.git
46
+ cd candela
47
+ pip install PyQt6
48
+ python candela.py
49
+ ```
50
+
51
+ ## Install via pip *(coming soon)*
52
+
53
+ ```bash
54
+ pip install candela-ctrl
55
+ candela
56
+ ```
57
+
58
+ ## Build a standalone binary
59
+
60
+ ```bash
61
+ pip install pyinstaller
62
+ pyinstaller --onefile --windowed --name candela candela.py
63
+ # Produces: dist/candela — single executable, no Python required
64
+ ```
65
+
66
+ ## Contributing
67
+
68
+ Issues and PRs welcome. If your monitor supports DDC/CI, hardware brightness control (via `ddcutil`) is the next planned feature — real backlight reduction instead of software gamma.
69
+
70
+ ## License
71
+
72
+ MIT
@@ -0,0 +1,389 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Candela - Manual brightness and color temperature control for Linux.
4
+ Requires: PyQt6, xrandr (xorg-xrandr), redshift
5
+ X11 only (not Wayland).
6
+ """
7
+
8
+ import sys
9
+ import json
10
+ import subprocess
11
+ from pathlib import Path
12
+ from PyQt6.QtWidgets import (
13
+ QApplication, QWidget, QVBoxLayout, QHBoxLayout,
14
+ QSlider, QLabel, QComboBox, QGroupBox, QPushButton, QCheckBox,
15
+ QSystemTrayIcon, QMenu
16
+ )
17
+ from PyQt6.QtGui import QIcon
18
+ from PyQt6.QtCore import Qt, QTimer
19
+
20
+
21
+ STYLESHEET = """
22
+ QWidget {
23
+ background-color: #1e1e2e;
24
+ color: #cdd6f4;
25
+ font-size: 13px;
26
+ font-family: 'Segoe UI', Ubuntu, sans-serif;
27
+ }
28
+ QGroupBox {
29
+ border: 1px solid #45475a;
30
+ border-radius: 6px;
31
+ margin-top: 10px;
32
+ padding: 14px 10px 10px 10px;
33
+ font-weight: bold;
34
+ color: #89b4fa;
35
+ }
36
+ QGroupBox::title {
37
+ subcontrol-origin: margin;
38
+ left: 10px;
39
+ padding: 0 5px;
40
+ }
41
+ QSlider::groove:horizontal {
42
+ height: 6px;
43
+ background: #45475a;
44
+ border-radius: 3px;
45
+ }
46
+ QSlider::handle:horizontal {
47
+ width: 20px;
48
+ height: 20px;
49
+ margin: -7px 0;
50
+ background: #89b4fa;
51
+ border-radius: 10px;
52
+ }
53
+ QSlider::sub-page:horizontal {
54
+ background: #89b4fa;
55
+ border-radius: 3px;
56
+ }
57
+ QComboBox {
58
+ background: #313244;
59
+ border: 1px solid #585b70;
60
+ border-radius: 4px;
61
+ padding: 4px 10px;
62
+ min-width: 120px;
63
+ }
64
+ QComboBox::drop-down { border: none; }
65
+ QPushButton {
66
+ background: #313244;
67
+ border: 1px solid #585b70;
68
+ border-radius: 4px;
69
+ padding: 5px 14px;
70
+ color: #cdd6f4;
71
+ }
72
+ QPushButton:hover { background: #45475a; }
73
+ QLabel#hint { color: #585b70; font-size: 11px; }
74
+ QLabel#value { color: #a6e3a1; font-weight: bold; min-width: 62px; }
75
+ QCheckBox { color: #6c7086; font-size: 11px; }
76
+ QCheckBox::indicator { width: 14px; height: 14px; border: 1px solid #585b70; border-radius: 3px; background: #313244; }
77
+ QCheckBox::indicator:checked { background: #89b4fa; border-color: #89b4fa; }
78
+ """
79
+
80
+
81
+ def get_monitors():
82
+ """Return connected output names from xrandr."""
83
+ try:
84
+ result = subprocess.run(
85
+ ["xrandr", "--query"], capture_output=True, text=True, timeout=3
86
+ )
87
+ return [
88
+ line.split()[0]
89
+ for line in result.stdout.splitlines()
90
+ if " connected" in line
91
+ ] or ["HDMI-1"]
92
+ except Exception:
93
+ return ["HDMI-1"]
94
+
95
+
96
+ def check_redshift():
97
+ """Return True if redshift is available on PATH."""
98
+ try:
99
+ subprocess.run(["redshift", "--help"], capture_output=True, timeout=2)
100
+ return True
101
+ except FileNotFoundError:
102
+ return False
103
+
104
+
105
+ CONFIG_PATH = Path.home() / ".config" / "candela" / "settings.json"
106
+ DEFAULTS = {"brightness": 100, "temp_k": 6500, "keep_on_close": False}
107
+
108
+
109
+ def load_config():
110
+ try:
111
+ return {**DEFAULTS, **json.loads(CONFIG_PATH.read_text())}
112
+ except Exception:
113
+ return dict(DEFAULTS)
114
+
115
+
116
+ def save_config(brightness, temp_k, keep_on_close):
117
+ try:
118
+ CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
119
+ CONFIG_PATH.write_text(json.dumps(
120
+ {"brightness": brightness, "temp_k": temp_k, "keep_on_close": keep_on_close},
121
+ indent=2
122
+ ))
123
+ except Exception:
124
+ pass
125
+
126
+
127
+ class MonitorControl(QWidget):
128
+ def __init__(self):
129
+ super().__init__()
130
+ self.monitors = get_monitors()
131
+ self.has_redshift = check_redshift()
132
+ self.config = load_config()
133
+
134
+ self.apply_timer = QTimer()
135
+ self.apply_timer.setSingleShot(True)
136
+ self.apply_timer.timeout.connect(self.apply_settings)
137
+
138
+ self._quitting = False
139
+
140
+ self.init_ui()
141
+ self.restore_config()
142
+ self.setup_tray()
143
+
144
+ if not self.has_redshift:
145
+ self.temp_group.setTitle("Color Temperature ⚠ (install redshift)")
146
+ self.temp_slider.setEnabled(False)
147
+
148
+ def init_ui(self):
149
+ self.setWindowTitle("Candela")
150
+ self.setMinimumWidth(400)
151
+ self.setMaximumWidth(520)
152
+ self.setStyleSheet(STYLESHEET)
153
+
154
+ root = QVBoxLayout(self)
155
+ root.setSpacing(10)
156
+ root.setContentsMargins(18, 18, 18, 18)
157
+
158
+ # --- Monitor selector ---
159
+ if len(self.monitors) > 1:
160
+ row = QHBoxLayout()
161
+ row.addWidget(QLabel("Monitor:"))
162
+ self.monitor_combo = QComboBox()
163
+ self.monitor_combo.addItems(self.monitors)
164
+ self.monitor_combo.currentIndexChanged.connect(self.schedule_apply)
165
+ row.addWidget(self.monitor_combo)
166
+ row.addStretch()
167
+ root.addLayout(row)
168
+ else:
169
+ self.monitor_combo = QComboBox()
170
+ self.monitor_combo.addItems(self.monitors)
171
+
172
+ # --- Brightness ---
173
+ bright_group = QGroupBox("Brightness")
174
+ bright_layout = QVBoxLayout(bright_group)
175
+
176
+ bright_row = QHBoxLayout()
177
+ self.brightness_slider = QSlider(Qt.Orientation.Horizontal)
178
+ self.brightness_slider.setRange(10, 100)
179
+ self.brightness_slider.setValue(100)
180
+ self.brightness_slider.valueChanged.connect(self.on_brightness_changed)
181
+
182
+ self.brightness_label = QLabel("100%")
183
+ self.brightness_label.setObjectName("value")
184
+ self.brightness_label.setAlignment(
185
+ Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
186
+ )
187
+
188
+ bright_row.addWidget(self.brightness_slider)
189
+ bright_row.addWidget(self.brightness_label)
190
+ bright_layout.addLayout(bright_row)
191
+
192
+ bright_hints = QHBoxLayout()
193
+ dim = QLabel("Dim")
194
+ dim.setObjectName("hint")
195
+ full = QLabel("Full")
196
+ full.setObjectName("hint")
197
+ full.setAlignment(Qt.AlignmentFlag.AlignRight)
198
+ bright_hints.addWidget(dim)
199
+ bright_hints.addWidget(full)
200
+ bright_layout.addLayout(bright_hints)
201
+
202
+ root.addWidget(bright_group)
203
+
204
+ # --- Color Temperature ---
205
+ self.temp_group = QGroupBox("Color Temperature")
206
+ temp_layout = QVBoxLayout(self.temp_group)
207
+
208
+ temp_row = QHBoxLayout()
209
+ self.temp_slider = QSlider(Qt.Orientation.Horizontal)
210
+ self.temp_slider.setRange(2000, 6500)
211
+ self.temp_slider.setValue(6500)
212
+ self.temp_slider.valueChanged.connect(self.on_temp_changed)
213
+
214
+ self.temp_label = QLabel("6500 K")
215
+ self.temp_label.setObjectName("value")
216
+ self.temp_label.setAlignment(
217
+ Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
218
+ )
219
+
220
+ temp_row.addWidget(self.temp_slider)
221
+ temp_row.addWidget(self.temp_label)
222
+ temp_layout.addLayout(temp_row)
223
+
224
+ temp_hints = QHBoxLayout()
225
+ warm = QLabel("Warm / Candlelight (2000K)")
226
+ warm.setObjectName("hint")
227
+ cool = QLabel("Daylight (6500K)")
228
+ cool.setObjectName("hint")
229
+ cool.setAlignment(Qt.AlignmentFlag.AlignRight)
230
+ temp_hints.addWidget(warm)
231
+ temp_hints.addWidget(cool)
232
+ temp_layout.addLayout(temp_hints)
233
+
234
+ root.addWidget(self.temp_group)
235
+
236
+ # --- Bottom row: keep-on-close checkbox + reset button ---
237
+ bottom_row = QHBoxLayout()
238
+
239
+ self.keep_checkbox = QCheckBox("Keep settings on close")
240
+ self.keep_checkbox.setChecked(False)
241
+ self.keep_checkbox.setToolTip(
242
+ "When unchecked, closing the window resets brightness and color temperature to defaults."
243
+ )
244
+ bottom_row.addWidget(self.keep_checkbox)
245
+
246
+ bottom_row.addStretch()
247
+
248
+ reset_btn = QPushButton("Reset to Defaults")
249
+ reset_btn.clicked.connect(self.reset_defaults)
250
+ bottom_row.addWidget(reset_btn)
251
+
252
+ root.addLayout(bottom_row)
253
+
254
+ # ------------------------------------------------------------------
255
+ # Slider callbacks
256
+
257
+ def on_brightness_changed(self, value):
258
+ self.brightness_label.setText(f"{value}%")
259
+ self.schedule_apply()
260
+
261
+ def on_temp_changed(self, value):
262
+ snapped = round(value / 100) * 100
263
+ self.temp_label.setText(f"{snapped} K")
264
+ self.schedule_apply()
265
+
266
+ def schedule_apply(self):
267
+ self.apply_timer.start(180)
268
+
269
+ # ------------------------------------------------------------------
270
+ # Apply
271
+
272
+ def apply_settings(self):
273
+ monitor = self.monitor_combo.currentText()
274
+ brightness = self.brightness_slider.value() / 100.0
275
+ temp_k = round(self.temp_slider.value() / 100) * 100
276
+
277
+ save_config(self.brightness_slider.value(), temp_k, self.keep_checkbox.isChecked())
278
+
279
+ if self.has_redshift:
280
+ # Redshift handles both — passing both in one call prevents
281
+ # them from clobbering each other's gamma ramp changes.
282
+ subprocess.Popen(
283
+ ["redshift", "-O", str(temp_k), "-b", f"{brightness:.2f}", "-P"],
284
+ stderr=subprocess.DEVNULL,
285
+ )
286
+ else:
287
+ # Fallback: xrandr brightness only, no color temp
288
+ subprocess.Popen(
289
+ ["xrandr", "--output", monitor, "--brightness", f"{brightness:.2f}"],
290
+ stderr=subprocess.DEVNULL,
291
+ )
292
+
293
+ def restore_config(self):
294
+ """Set sliders to last saved values without triggering redundant apply."""
295
+ self.brightness_slider.blockSignals(True)
296
+ self.temp_slider.blockSignals(True)
297
+ self.brightness_slider.setValue(self.config["brightness"])
298
+ self.temp_slider.setValue(self.config["temp_k"])
299
+ self.brightness_label.setText(f"{self.config['brightness']}%")
300
+ self.temp_label.setText(f"{self.config['temp_k']} K")
301
+ self.keep_checkbox.setChecked(self.config["keep_on_close"])
302
+ self.brightness_slider.blockSignals(False)
303
+ self.temp_slider.blockSignals(False)
304
+
305
+ def setup_tray(self):
306
+ icon = QIcon.fromTheme("display-brightness",
307
+ QIcon.fromTheme("video-display"))
308
+ self.tray = QSystemTrayIcon(icon, self)
309
+ self.tray.setToolTip("Candela")
310
+
311
+ menu = QMenu()
312
+ show_action = menu.addAction("Show Candela")
313
+ show_action.triggered.connect(self.show_window)
314
+ menu.addSeparator()
315
+ quit_action = menu.addAction("Quit")
316
+ quit_action.triggered.connect(self.quit_app)
317
+
318
+ self.tray.setContextMenu(menu)
319
+ self.tray.activated.connect(self.on_tray_activated)
320
+ self.tray.show()
321
+
322
+ def show_window(self):
323
+ self.show()
324
+ self.raise_()
325
+ self.activateWindow()
326
+
327
+ def on_tray_activated(self, reason):
328
+ if reason == QSystemTrayIcon.ActivationReason.Trigger:
329
+ if self.isVisible():
330
+ self.hide()
331
+ else:
332
+ self.show_window()
333
+
334
+ def quit_app(self):
335
+ self._quitting = True
336
+ if not self.keep_checkbox.isChecked():
337
+ if self.has_redshift:
338
+ subprocess.run(
339
+ ["redshift", "-O", "6500", "-b", "1.00", "-P"],
340
+ stderr=subprocess.DEVNULL,
341
+ )
342
+ else:
343
+ monitor = self.monitor_combo.currentText()
344
+ subprocess.run(
345
+ ["xrandr", "--output", monitor, "--brightness", "1.0"],
346
+ stderr=subprocess.DEVNULL,
347
+ )
348
+ save_config(100, 6500, self.keep_checkbox.isChecked())
349
+ self.tray.hide()
350
+ QApplication.instance().quit()
351
+
352
+ def reset_defaults(self):
353
+ self.brightness_slider.setValue(100)
354
+ self.temp_slider.setValue(6500)
355
+ # reset_defaults triggers apply_settings via the slider signals
356
+
357
+ def closeEvent(self, event):
358
+ if not self._quitting and self.tray.isSystemTrayAvailable():
359
+ self.hide()
360
+ event.ignore()
361
+ return
362
+
363
+ if not self.keep_checkbox.isChecked():
364
+ # Bypass the debounce timer — call subprocess.run (blocking)
365
+ # directly so the reset completes before the process exits.
366
+ if self.has_redshift:
367
+ subprocess.run(
368
+ ["redshift", "-O", "6500", "-b", "1.00", "-P"],
369
+ stderr=subprocess.DEVNULL,
370
+ )
371
+ else:
372
+ monitor = self.monitor_combo.currentText()
373
+ subprocess.run(
374
+ ["xrandr", "--output", monitor, "--brightness", "1.0"],
375
+ stderr=subprocess.DEVNULL,
376
+ )
377
+ save_config(100, 6500, self.keep_checkbox.isChecked())
378
+ event.accept()
379
+
380
+
381
+ def main():
382
+ app = QApplication(sys.argv)
383
+ window = MonitorControl()
384
+ window.show()
385
+ sys.exit(app.exec())
386
+
387
+
388
+ if __name__ == "__main__":
389
+ main()
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: candela-ctrl
3
+ Version: 0.1.0
4
+ Summary: Manual brightness and color temperature control for Linux
5
+ Author: Jon Blanchard
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jfblanchard/candela
8
+ Project-URL: Repository, https://github.com/jfblanchard/candela
9
+ Keywords: brightness,monitor,color-temperature,linux,night-mode,redshift,display
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: X11 Applications :: Qt
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Desktop Environment
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: PyQt6
21
+ Dynamic: license-file
22
+
23
+ # Candela
24
+
25
+ > Manual brightness and color temperature control for Linux — named after the SI unit of luminous intensity.
26
+
27
+ A clean, dark-themed desktop utility for tuning your monitor at night (or any time). Two sliders: one for brightness, one for color temperature in Kelvin. No automatic scheduling, no daemons — just instant manual control when you want it.
28
+
29
+ ![screenshot placeholder](screenshot.png)
30
+
31
+ ## Why Candela?
32
+
33
+ Most Linux tools in this space are either time-based and automatic (Redshift, Gammastep), command-line only (`xrandr`, `sct`), or have dated UIs that expose raw R/G/B gamma channels instead of a human-friendly Kelvin temperature slider. Candela aims to be the tool you reach for when you just want to quickly dim your screen and warm the color for late-night use.
34
+
35
+ ## Features
36
+
37
+ - **Brightness slider** — 10% to 100%
38
+ - **Color temperature slider** — 2000K (warm candlelight) to 6500K (daylight)
39
+ - **Multi-monitor support** — dropdown auto-populated from connected outputs
40
+ - **Instant apply** — changes take effect as you move the slider
41
+ - **Settings persist** after closing, within your X session (until logout/reboot)
42
+ - **Reset to Defaults** button restores 6500K / 100% brightness
43
+ - Dark UI — easy on the eyes when you need it most
44
+
45
+ ## Requirements
46
+
47
+ **Python:** 3.10+
48
+
49
+ **Python packages:**
50
+ ```bash
51
+ pip install PyQt6
52
+ ```
53
+
54
+ **System packages:**
55
+ ```bash
56
+ sudo apt install redshift # Debian / Ubuntu / Mint
57
+ sudo dnf install redshift # Fedora
58
+ sudo pacman -S redshift # Arch / Manjaro
59
+ ```
60
+ `redshift` handles color temperature. Brightness works without it (software gamma via `xrandr`).
61
+
62
+ > **Note:** X11 only. Wayland is not currently supported.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ git clone https://github.com/jfblanchard/candela.git
68
+ cd candela
69
+ pip install PyQt6
70
+ python candela.py
71
+ ```
72
+
73
+ ## Install via pip *(coming soon)*
74
+
75
+ ```bash
76
+ pip install candela-ctrl
77
+ candela
78
+ ```
79
+
80
+ ## Build a standalone binary
81
+
82
+ ```bash
83
+ pip install pyinstaller
84
+ pyinstaller --onefile --windowed --name candela candela.py
85
+ # Produces: dist/candela — single executable, no Python required
86
+ ```
87
+
88
+ ## Contributing
89
+
90
+ Issues and PRs welcome. If your monitor supports DDC/CI, hardware brightness control (via `ddcutil`) is the next planned feature — real backlight reduction instead of software gamma.
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ candela.py
4
+ pyproject.toml
5
+ candela_ctrl.egg-info/PKG-INFO
6
+ candela_ctrl.egg-info/SOURCES.txt
7
+ candela_ctrl.egg-info/dependency_links.txt
8
+ candela_ctrl.egg-info/entry_points.txt
9
+ candela_ctrl.egg-info/requires.txt
10
+ candela_ctrl.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ candela = candela:main
@@ -0,0 +1 @@
1
+ candela
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "candela-ctrl"
7
+ version = "0.1.0"
8
+ description = "Manual brightness and color temperature control for Linux"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [{name = "Jon Blanchard"}]
12
+ requires-python = ">=3.10"
13
+ dependencies = ["PyQt6"]
14
+ keywords = ["brightness", "monitor", "color-temperature", "linux", "night-mode", "redshift", "display"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: X11 Applications :: Qt",
18
+ "Intended Audience :: End Users/Desktop",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Desktop Environment",
22
+ "Topic :: Utilities",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/jfblanchard/candela"
27
+ Repository = "https://github.com/jfblanchard/candela"
28
+
29
+ [project.scripts]
30
+ candela = "candela:main"
31
+
32
+ [tool.setuptools]
33
+ py-modules = ["candela"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+