feat: add laptop/display brightness modulation

This commit is contained in:
daniel committed 2026-10-02 12:14:40 +02:00
1 parent ef1e69ae83
commit 7979acc2eb
34 files changed
+1308 -78

No files matched your search

+212
View File
@@ -0,0 +1,212 @@
/*
* AcpiVideo.cpp
* ACPI video extensions (_BCL/_BCM/_BQC) panel brightness fallback
* Copyright (c) 2026 Daniel Hammer
*/
#include "AcpiVideo.hpp"
#include "AcpiPower.hpp"
#include <ACPI/AML/AmlInterpreter.hpp>
#include <Pci/Pci.hpp>
#include <CppLib/Spinlock.hpp>
#include <CppLib/Stream.hpp>
#include <Terminal/Terminal.hpp>
using namespace Kt;
namespace Hal {
namespace AcpiVideo {
// ============================================================================
// State
// ============================================================================
static constexpr int MaxLevels = 101;
static kcp::Mutex g_lock;
static bool g_probed = false;
static int32_t g_output = -1; // output device carrying _BCM
static bool g_hasBqc = false;
static uint32_t g_levels[MaxLevels]; // ascending, de-duplicated
static int g_levelCount = 0;
static int g_percent = -1; // last level set, when no _BQC
// ============================================================================
// Discovery
// ============================================================================
static bool HasMethod(int32_t node, const char* name) {
return AML::GetInterpreter().FindChild(node, name) >= 0;
}
// _ADR display output IDs (ACPI 6.x, B.4.2): bits 11:8 are the type,
// 4 = internal flat panel. The panel is the output to drive.
static int32_t FindOutput() {
auto& interp = AML::GetInterpreter();
auto& ns = interp.GetNamespace();
int32_t fallback = -1;
auto& devices = Pci::GetDevices();
for (uint64_t i = 0; i < devices.size(); i++) {
const auto& d = devices[i];
if (d.ClassCode != 0x03) continue;
int32_t gpu = AcpiPower::FindPciDevice(d.Bus, d.Device, d.Function);
if (gpu < 0) continue;
int32_t panel = -1;
ns.ForEachChild(gpu, AML::ObjectType::Device,
[&](int32_t idx, const AML::NamespaceNode*) {
if (!HasMethod(idx, "_BCM") || !HasMethod(idx, "_BCL")) return true;
AML::Object adr{};
uint64_t id = interp.EvaluateChild(idx, "_ADR", nullptr, 0, adr)
? AcpiPower::ResultInteger(adr) : 0;
if (((id >> 8) & 0xF) == 0x4) {
panel = idx;
return false;
}
if (fallback < 0) fallback = idx;
return true;
});
if (panel >= 0) return panel;
}
if (fallback >= 0) return fallback;
// Some firmware hangs the output device off a bridge the PCI scan
// could not map; any _BCM in the namespace is the only one there is.
ns.WalkDescendants(ns.RootIndex(), AML::ObjectType::Method,
[&](int32_t idx, const AML::NamespaceNode* n) {
if (fallback >= 0 || n->Name[0] != '_' || n->Name[1] != 'B'
|| n->Name[2] != 'C' || n->Name[3] != 'M')
return;
const AML::NamespaceNode* node = ns.GetNode(idx);
if (node && node->ParentIndex >= 0 && HasMethod(node->ParentIndex, "_BCL"))
fallback = node->ParentIndex;
});
return fallback;
}
// _BCL: { level on AC, level on battery, supported levels... }. Only
// the supported list counts; firmware often repeats or misorders it.
static bool ReadLevels() {
auto& interp = AML::GetInterpreter();
AML::Object bcl{};
if (!interp.EvaluateChild(g_output, "_BCL", nullptr, 0, bcl)
|| bcl.Type != AML::ObjectType::Package)
return false;
g_levelCount = 0;
int count = interp.GetPackageCount(bcl);
for (int i = 2; i < count; i++) {
AML::Object elem{};
if (!interp.GetPackageElement(bcl, i, elem)
|| elem.Type != AML::ObjectType::Integer)
continue;
uint32_t level = (uint32_t)elem.Integer;
int pos = 0;
while (pos < g_levelCount && g_levels[pos] < level) pos++;
if (pos < g_levelCount && g_levels[pos] == level) continue;
if (g_levelCount == MaxLevels) break;
for (int j = g_levelCount; j > pos; j--) g_levels[j] = g_levels[j - 1];
g_levels[pos] = level;
g_levelCount++;
}
return g_levelCount >= 2 && g_levels[g_levelCount - 1] > 0;
}
static void Probe() {
g_probed = true;
auto& interp = AML::GetInterpreter();
if (!interp.IsInitialized()) return;
g_output = FindOutput();
if (g_output < 0) return;
if (!ReadLevels()) {
g_output = -1;
return;
}
g_hasBqc = HasMethod(g_output, "_BQC");
// Without _BQC the level is only known once we set one; until
// then firmware's boot level is assumed to be full brightness.
if (!g_hasBqc) g_percent = 100;
char path[128];
interp.GetNamespace().GetNodePath(g_output, path, sizeof(path));
KernelLogStream(INFO, "AcpiVideo") << "Brightness via " << path
<< "._BCM, " << base::dec << (uint64_t)g_levelCount << " levels"
<< (g_hasBqc ? ", _BQC" : "");
}
// ============================================================================
// Levels
// ============================================================================
static int LevelToPercent(uint32_t level) {
uint32_t top = g_levels[g_levelCount - 1];
if (level > top) level = top;
return (int)(((uint64_t)level * 100 + top / 2) / top);
}
// The supported level closest to the requested share of the top one.
static uint32_t PercentToLevel(int percent) {
uint64_t target = (uint64_t)g_levels[g_levelCount - 1] * (uint32_t)percent / 100;
uint32_t best = g_levels[0];
uint64_t bestDistance = ~0ULL;
for (int i = 0; i < g_levelCount; i++) {
uint64_t distance = g_levels[i] > target ? g_levels[i] - target
: target - g_levels[i];
if (distance < bestDistance) {
bestDistance = distance;
best = g_levels[i];
}
}
return best;
}
static int Query() {
if (g_hasBqc) {
AML::Object result{};
if (AML::GetInterpreter().EvaluateChild(g_output, "_BQC", nullptr, 0, result)
&& result.Type == AML::ObjectType::Integer)
return LevelToPercent((uint32_t)result.Integer);
}
return g_percent;
}
// ============================================================================
// Public API
// ============================================================================
int Brightness(int percent) {
g_lock.Acquire();
if (!g_probed) Probe();
if (g_output < 0) {
g_lock.Release();
return -1;
}
int result;
if (percent < 0) {
result = Query();
} else {
if (percent > 100) percent = 100;
uint32_t level = PercentToLevel(percent);
AML::Object arg = AcpiPower::IntegerArg(level);
AML::Object ignored{};
if (AML::GetInterpreter().EvaluateChild(g_output, "_BCM", &arg, 1, ignored)) {
g_percent = LevelToPercent(level);
int reported = Query();
result = reported >= 0 ? reported : g_percent;
} else {
result = -2;
}
}
g_lock.Release();
return result;
}
};
};
+23
View File
@@ -0,0 +1,23 @@
/*
* AcpiVideo.hpp
* ACPI video extensions (_BCL/_BCM/_BQC) panel brightness fallback
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Hal {
namespace AcpiVideo {
// Panel brightness through the firmware's ACPI video output device,
// for machines no native backlight driver covers (a panel wired to a
// discrete GPU, a GPU Montauk has no driver for). percent < 0 queries;
// otherwise the nearest level the firmware lists is selected. Returns
// the resulting 0..100, -1 when the firmware has no brightness
// methods, -2 when evaluating them failed. Discovery runs on first
// use. May sleep (AML).
int Brightness(int percent);
};
};
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 283
#define MONTAUK_BUILD_NUMBER 287
+29 -2
View File
@@ -11,6 +11,7 @@
#include <Memory/HHDM.hpp>
#include <Graphics/Framebuffer.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <ACPI/AcpiVideo.hpp>
#include <Terminal/Terminal.hpp>
#include "Syscall.hpp"
@@ -128,8 +129,30 @@ namespace montauk::abi {
return GPU::Flip((int)index, (flags & 1) != 0, proc->pid);
}
// The firmware's ACPI brightness methods drive the built-in panel, so
// they stand in only when that panel is what is on screen (or no native
// driver knows what is): never for an external monitor without DDC/CI.
static bool AcpiBrightnessApplies() {
if (!Drivers::Graphics::IntelGPU::IsInitialized()) return true;
uint8_t type = Drivers::Graphics::IntelGPU::ActiveConnectorType();
return type == DISPLAY_CONNECTOR_EDP || type == DISPLAY_CONNECTOR_LVDS;
}
static int DisplayBrightness(int percent) {
int rc = Drivers::Graphics::IntelGPU::Brightness(percent);
if (rc == -1 && AcpiBrightnessApplies())
rc = Hal::AcpiVideo::Brightness(percent);
return rc;
}
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
return Drivers::Graphics::IntelGPU::GetDisplayInfo(out) ? Success : NoDevice;
if (!Drivers::Graphics::IntelGPU::GetDisplayInfo(out)) return NoDevice;
if (out->brightness < 0 && AcpiBrightnessApplies()) {
out->brightness = Hal::AcpiVideo::Brightness(-1);
if (out->brightness >= 0) out->capabilities |= DISPLAY_CAP_BRIGHTNESS;
else out->brightness = -1;
}
return Success;
}
static int64_t Sys_DisplayModes(DisplayModeInfo* out, int maxCount) {
@@ -145,8 +168,12 @@ namespace montauk::abi {
return rc;
}
// percent < 0 queries. Returns 0..100, NoDevice when the active output
// has no controllable backlight, IoFailure when the hardware refused.
static int64_t Sys_DisplayBrightness(int percent) {
return Drivers::Graphics::IntelGPU::Brightness(percent);
int rc = DisplayBrightness(percent);
if (rc >= 0) return rc;
return rc == -1 ? NoDevice : IoFailure;
}
static uint64_t Sys_TermSize() {
+1 -1
View File
@@ -207,7 +207,7 @@ namespace montauk::abi {
return Sys_DisplaySetMode((int)frame->arg1);
case SYS_DISPLAYBRIGHTNESS:
if ((int64_t)frame->arg1 >= 0 &&
!Sched::Consent::Require(CAP_DISPLAY_ADMIN)) return CapabilityDenied;
!Sched::Consent::Require(CAP_BRIGHTNESS)) return CapabilityDenied;
return Sys_DisplayBrightness((int)frame->arg1);
case SYS_GETEXECPATH:
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return BadPointer;
+9 -3
View File
@@ -385,8 +385,14 @@ namespace montauk::abi {
never part of a session mask, never part of "all", and never
delegated by the compositor to what it launches. */
static constexpr uint64_t CAP_COMPOSITOR = 1ULL << 13;
static constexpr uint64_t CAP_ALL = (1ULL << 14) - 1;
static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND;
/* Set the backlight of the active display (SYS_DISPLAYBRIGHTNESS).
A seat preference rather than display administration, so every
session holds it; CAP_DISPLAY_ADMIN keeps mode setting. Reading the
level needs nothing. */
static constexpr uint64_t CAP_BRIGHTNESS = 1ULL << 14;
static constexpr uint64_t CAP_ALL = (1ULL << 15) - 1;
static constexpr uint64_t CAP_STANDARD_SESSION =
CAP_POWER_REQUEST | CAP_SUSPEND | CAP_BRIGHTNESS;
static constexpr uint64_t CAP_ADMIN_SESSION =
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
@@ -438,7 +444,7 @@ namespace montauk::abi {
deferred delegation, not amplification. */
static constexpr uint64_t CAP_CONSENT_ELIGIBLE =
CAP_STORAGE_ADMIN | CAP_NETWORK_ADMIN | CAP_SET_TIME |
CAP_DISPLAY_ADMIN | CAP_LOG_READ;
CAP_DISPLAY_ADMIN | CAP_LOG_READ | CAP_BRIGHTNESS;
static_assert((CAP_CONSENT_ELIGIBLE & ~CAP_ADMIN_SESSION) == 0,
"consent can only hand out authority an admin session holds");
+602 -47
View File
@@ -82,8 +82,7 @@ namespace Drivers::Graphics::IntelGPU {
static uint8_t g_connectorType = montauk::abi::DISPLAY_CONNECTOR_UNKNOWN;
static bool g_connectorConnected = false;
static bool g_edidValid = false;
static bool g_pwmSupported = false;
static uint32_t g_pwmCtlReg = 0;
static int g_ddiPort = -1; // DDI port driving the output, -1 unknown
static char g_connectorName[32] = "Unknown";
static char g_monitorName[64] = "Unknown display";
static uint8_t g_auxEdid[256] = {};
@@ -556,10 +555,11 @@ namespace Drivers::Graphics::IntelGPU {
}
static bool AuxI2cTransfer(uint8_t request, const uint8_t* writeData,
int writeSize, uint8_t* readData, int readSize) {
int writeSize, uint8_t* readData, int readSize,
uint8_t i2cAddress = 0x50) {
for (int retry = 0; retry < 16; retry++) {
uint8_t reply = 0xFF;
int result = AuxTransfer(request, 0x50, writeData, writeSize,
int result = AuxTransfer(request, i2cAddress, writeData, writeSize,
readData, readSize, &reply);
uint8_t nativeReply = reply & 0x3;
uint8_t i2cReply = reply & 0xC;
@@ -600,7 +600,8 @@ namespace Drivers::Graphics::IntelGPU {
static bool ReadDpEdid() {
g_auxEdidSize = 0;
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_DP
if ((g_connectorType != montauk::abi::DISPLAY_CONNECTOR_DP
&& g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP)
|| g_auxControlReg == 0)
return false;
@@ -789,9 +790,16 @@ namespace Drivers::Graphics::IntelGPU {
g_connectorConnected = (ReadReg(trans.conf) & PIPECONF_ENABLE) != 0;
uint32_t ddi = ReadReg(trans.ddi);
g_auxControlReg = 0;
g_ddiPort = -1;
if (g_gpuGen <= 7) {
if (ReadReg(LVDS) & LVDS_PORT_ENABLE) {
// Haswell (listed as Gen 7) has the eDP transcoder; Ironlake to
// Ivy Bridge drive eDP from the CPU's port A.
if (trans.edp || (ReadReg(DP_A) & (1u << 31))) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP;
CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1");
g_ddiPort = 0;
} else if (ReadReg(LVDS) & LVDS_PORT_ENABLE) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_LVDS;
CopyText(g_connectorName, sizeof(g_connectorName), "LVDS-1");
} else if (ReadReg(ADPA) & ADPA_DAC_ENABLE) {
@@ -807,14 +815,30 @@ namespace Drivers::Graphics::IntelGPU {
} else if (trans.edp) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP;
CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1");
g_ddiPort = 0;
g_auxControlReg = DP_AUX_A_CTL;
} else {
uint32_t mode = (ddi & TRANS_DDI_MODE_MASK) >> 24;
uint32_t port = (ddi & TRANS_DDI_PORT_MASK) >> 27;
const char* suffix = port == 0 ? "A" : port == 1 ? "B"
: port == 2 ? "C" : port == 3 ? "D"
: port == 8 ? "TC1" : port == 9 ? "TC2"
: port == 10 ? "TC3" : port == 11 ? "TC4"
: "unknown";
// Gen12 widened the port select to bits 30:27 and stores port + 1
// (0 = none); earlier DDI parts store the port in bits 30:28.
// Gen12 numbering puts the Type-C ports from D (index 3) onward.
int port;
if (g_gpuGen >= 12) {
uint32_t field = (ddi & TRANS_DDI_PORT_MASK) >> 27;
port = field ? (int)field - 1 : -1;
} else {
port = (int)((ddi >> 28) & 0x7);
}
g_ddiPort = port;
static const char* const legacyNames[] = {"A", "B", "C", "D", "E", "F"};
static const char* const tcNames[] = {"TC1", "TC2", "TC3", "TC4", "TC5", "TC6"};
const char* suffix = "unknown";
if (port >= 0 && g_gpuGen >= 12) {
if (port < 3) suffix = legacyNames[port];
else if (port - 3 < 6) suffix = tcNames[port - 3];
} else if (port >= 0 && port < 6) {
suffix = legacyNames[port];
}
if (mode == 0) {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI;
CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-");
@@ -824,11 +848,9 @@ namespace Drivers::Graphics::IntelGPU {
} else {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP;
CopyText(g_connectorName, sizeof(g_connectorName), "DP-");
static constexpr uint32_t auxControls[] = {
DP_AUX_A_CTL, DP_AUX_B_CTL, DP_AUX_C_CTL, DP_AUX_D_CTL,
};
if (port < sizeof(auxControls) / sizeof(auxControls[0]))
g_auxControlReg = auxControls[port];
// AUX channels are laid out per port, Type-C ones included.
if (port >= 0 && port <= 8)
g_auxControlReg = DP_AUX_A_CTL + (uint32_t)port * 0x100;
}
uint64_t len = 0;
while (len + 1 < sizeof(g_connectorName) && g_connectorName[len]) len++;
@@ -913,25 +935,544 @@ namespace Drivers::Graphics::IntelGPU {
}
}
static void DiscoverPwm() {
g_pwmSupported = false;
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP
&& g_connectorType != montauk::abi::DISPLAY_CONNECTOR_LVDS)
return;
// =========================================================================
// Backlight
// =========================================================================
uint32_t pch = ReadReg(BLC_PWM_PCH_CTL2);
if ((pch >> 16) != 0) {
g_pwmCtlReg = BLC_PWM_PCH_CTL2;
g_pwmSupported = true;
// South-display family. The PWM register layout and the GMBUS pin map
// follow the PCH, not the GPU: a Coffee Lake on Cannon Point and a Kaby
// Lake on Sunrise Point are both Gen 9 but have incompatible PWM blocks.
enum class PchFamily : uint8_t { Unknown, Ibx, Lpt, Cnp, Icp };
enum class BacklightMethod : uint8_t { None, PwmIlk, PwmLpt, PwmCnp, DpcdAux, DdcCi };
static PchFamily g_pch = PchFamily::Unknown;
static BacklightMethod g_blMethod = BacklightMethod::None;
static uint32_t g_blFreqReg = 0; // PWM period register
static uint32_t g_blDutyReg = 0; // PWM duty register
static uint32_t g_blMax = 0; // DPCD / DDC full-scale value
static int g_blPercent = -1; // last level read or written
static bool g_dpcdModePending = false; // panel still on its PWM pin
static bool g_ddcCandidate = false; // external monitor, DDC/CI untried
static uint64_t g_ddcRetryMs = 0; // earliest re-probe after a failure
static uint64_t g_ddcLastMs = 0; // MCCS needs 50ms between commands
// Serializes backlight access: DDC/CI and DPCD changes are multi-step
// AUX/GMBUS transactions that must not interleave.
static kcp::Mutex g_blLock;
// Lowest level a panel is driven to. A zero duty cycle turns some panels
// fully dark, which leaves nothing on screen to turn them back up with.
static constexpr int MinPanelPercent = 1;
// MCCS DDC/CI (VESA Monitor Control Command Set over I2C address 0x37).
static constexpr uint8_t DdcAddress = 0x37;
static constexpr uint8_t DdcHostAddress = 0x51;
static constexpr uint8_t DdcVcpBrightness = 0x10;
static constexpr uint64_t DdcCommandGapMs = 50;
static constexpr uint64_t DdcReplyDelayMs = 40;
static constexpr uint64_t DdcRetryIntervalMs = 60000;
static constexpr uint32_t SOUTH_DSPCLK_GATE_D = 0xC2020;
static constexpr uint32_t PCH_GMBUSUNIT_CLOCK_GATE_DISABLE = (1u << 31);
static PchFamily DetectPch() {
auto& devices = Pci::GetDevices();
for (uint64_t i = 0; i < devices.size(); i++) {
const auto& d = devices[i];
if (d.VendorId != VendorIntel || d.Bus != 0 || d.Device != 0x1F
|| d.Function != 0)
continue;
// Device-ID families as i915 groups them (id & 0xFF80).
switch (d.DeviceId & 0xFF80) {
case 0x3B00: case 0x1C00: case 0x1E00:
return PchFamily::Ibx;
case 0x8C00: case 0x9C00: case 0x8C80: case 0x9C80: // LPT/WPT
case 0xA100: case 0x9D00: case 0xA280: case 0xA380: // SPT/KBP/CMP-V
return PchFamily::Lpt;
case 0xA300: case 0x9D80: case 0x0280: case 0x0680: // CNP/CMP
return PchFamily::Cnp;
default:
break;
}
break;
}
// Ice Point and everything after it share the Cannon Point PWM block
// and the Ice Point pin map, and new PCH IDs keep appearing.
return g_gpuGen >= 11 ? PchFamily::Icp : PchFamily::Unknown;
}
static int RawToPercent(uint32_t raw, uint32_t maximum) {
if (maximum == 0) return -1;
if (raw > maximum) raw = maximum;
return (int)(((uint64_t)raw * 100 + maximum / 2) / maximum);
}
static uint32_t PercentToRaw(int percent, uint32_t maximum) {
return (uint32_t)(((uint64_t)maximum * (uint32_t)percent + 50) / 100);
}
static void ShortSpin() {
for (int i = 0; i < 20000; i++) asm volatile("pause");
}
// ---- Panel PWM ----------------------------------------------------------
static bool PwmState(uint32_t& maximum, uint32_t& duty) {
uint32_t freq = ReadReg(g_blFreqReg);
uint32_t value = ReadReg(g_blDutyReg);
if (freq == 0xFFFFFFFF) return false;
switch (g_blMethod) {
case BacklightMethod::PwmCnp:
maximum = freq;
duty = value;
break;
case BacklightMethod::PwmLpt:
maximum = value >> 16;
duty = value & 0xFFFF;
break;
case BacklightMethod::PwmIlk:
maximum = freq >> 16;
duty = value & 0xFFFF;
break;
default:
return false;
}
return maximum != 0;
}
static void PwmWriteDuty(uint32_t duty) {
if (g_blMethod == BacklightMethod::PwmCnp) {
WriteReg(g_blDutyReg, duty);
} else {
uint32_t cpu = ReadReg(BLC_PWM_CPU_CTL);
if ((cpu >> 16) != 0) {
g_pwmCtlReg = BLC_PWM_CPU_CTL;
g_pwmSupported = true;
uint32_t value = ReadReg(g_blDutyReg);
WriteReg(g_blDutyReg, (value & 0xFFFF0000u) | (duty & 0xFFFF));
}
(void)ReadReg(g_blDutyReg);
}
// Adopt the PWM controller firmware left driving the panel. A controller
// that is not enabled is not what lights this panel, so it is skipped.
static bool DiscoverPwmBacklight() {
bool cnpLayout = g_pch == PchFamily::Cnp || g_pch == PchFamily::Icp;
if (g_pch == PchFamily::Unknown && g_gpuGen >= 8) {
// Unrecognized bridge: the LPT layout has nothing at DUTY1, so a
// plausible duty there identifies the CNP block.
uint32_t freq = ReadReg(BXT_BLC_PWM_FREQ1);
uint32_t duty = ReadReg(BXT_BLC_PWM_DUTY1);
cnpLayout = duty != 0 && duty != 0xFFFFFFFF && duty <= freq;
}
if (cnpLayout) {
// BXT/CNP firmware may route the panel through either controller.
for (uint32_t c = 0; c < 2; c++) {
uint32_t delta = c * BXT_BLC_PWM_CONTROLLER_DELTA;
uint32_t ctl = ReadReg(BXT_BLC_PWM_CTL1 + delta);
uint32_t freq = ReadReg(BXT_BLC_PWM_FREQ1 + delta);
if (ctl == 0xFFFFFFFF || !(ctl & BLC_PWM_ENABLE)
|| freq == 0 || freq == 0xFFFFFFFF)
continue;
g_blMethod = BacklightMethod::PwmCnp;
g_blFreqReg = BXT_BLC_PWM_FREQ1 + delta;
g_blDutyReg = BXT_BLC_PWM_DUTY1 + delta;
return true;
}
return false;
}
if (g_pch == PchFamily::Lpt || g_gpuGen >= 8) {
uint32_t ctl1 = ReadReg(BLC_PWM_PCH_CTL1);
uint32_t ctl2 = ReadReg(BLC_PWM_PCH_CTL2);
if (ctl1 == 0xFFFFFFFF || !(ctl1 & BLC_PWM_ENABLE) || (ctl2 >> 16) == 0)
return false;
g_blMethod = BacklightMethod::PwmLpt;
g_blFreqReg = BLC_PWM_PCH_CTL2;
g_blDutyReg = BLC_PWM_PCH_CTL2;
return true;
}
uint32_t pch2 = ReadReg(BLC_PWM_PCH_CTL2);
uint32_t cpu2 = ReadReg(BLC_PWM_CPU_CTL2);
if (pch2 == 0xFFFFFFFF || (pch2 >> 16) == 0 || !(cpu2 & BLC_PWM_ENABLE))
return false;
g_blMethod = BacklightMethod::PwmIlk;
g_blFreqReg = BLC_PWM_PCH_CTL2;
g_blDutyReg = BLC_PWM_CPU_CTL;
return true;
}
// ---- eDP DPCD backlight -------------------------------------------------
// Native AUX read or write of DPCD registers, retrying sink DEFERs.
static bool DpcdAccess(bool write, uint32_t address, uint8_t* data, int size) {
for (int attempt = 0; attempt < 8; attempt++) {
uint8_t reply = 0xFF;
int result = write
? AuxTransfer(0x8, address, data, size, nullptr, 0, &reply)
: AuxTransfer(0x9, address, nullptr, 0, data, size, &reply);
if (result == size && (reply & 0x3) == 0) return true;
if (result < 0 || (reply & 0x3) != 0x2) return false;
ShortSpin();
}
return false;
}
// Before Gen 12 the eDP output has its own transcoder; from Gen 12 it is
// an ordinary DDI in DP mode, so ask the sink. Only eDP sinks implement
// the 0x700 eDP register block or EDP_CONFIGURATION_CAP.
static void DetectEmbeddedDp() {
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_DP || g_auxControlReg == 0)
return;
uint8_t edpRev = 0;
uint8_t edpConfig = 0;
bool edp = DpcdAccess(false, DPCD_EDP_REV, &edpRev, 1) && edpRev != 0;
if (!edp) edp = DpcdAccess(false, 0x00D, &edpConfig, 1) && edpConfig != 0;
if (!edp) return;
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP;
CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1");
KernelLogStream(INFO, "IntelGPU") << "DDI output identified as eDP (eDP DPCD rev "
<< base::hex << (uint64_t)edpRev << ")";
}
static int DpcdBrightnessBytes() {
return g_blMax > 0xFF ? 2 : 1;
}
// Returns 0 when the panel offers no AUX brightness control, 1 when it
// does but is still driven from its PWM pin, 2 when DPCD already owns it.
static int ProbeDpcdBacklight() {
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP || g_auxControlReg == 0)
return 0;
uint8_t caps[3] = {};
if (!DpcdAccess(false, DPCD_EDP_REV, caps, 3)) return 0;
if (caps[0] == 0 || !(caps[2] & EDP_BLCAP_AUX_SET)) return 0;
uint8_t mode = 0;
if (!DpcdAccess(false, DPCD_EDP_BACKLIGHT_MODE, &mode, 1)) return 0;
g_blMax = (caps[2] & EDP_BLCAP_BYTE_COUNT_16) ? 0xFFFF : 0xFF;
return (mode & EDP_BLMODE_MASK) == EDP_BLMODE_DPCD ? 2 : 1;
}
static int DpcdReadPercent() {
uint8_t bytes[2] = {};
int count = DpcdBrightnessBytes();
if (!DpcdAccess(false, DPCD_EDP_BRIGHTNESS_MSB, bytes, count)) return -1;
uint32_t raw = count == 2 ? ((uint32_t)bytes[0] << 8 | bytes[1]) : bytes[0];
return RawToPercent(raw, g_blMax);
}
static bool DpcdWritePercent(int percent) {
uint32_t raw = PercentToRaw(percent, g_blMax);
if (raw == 0) raw = 1;
uint8_t bytes[2];
int count = DpcdBrightnessBytes();
if (count == 2) {
bytes[0] = (uint8_t)(raw >> 8);
bytes[1] = (uint8_t)raw;
} else {
bytes[0] = (uint8_t)raw;
}
if (!DpcdAccess(true, DPCD_EDP_BRIGHTNESS_MSB, bytes, count)) return false;
if (!g_dpcdModePending) return true;
// First change on a panel that was on its PWM pin: the level is
// already programmed, so switching the source cannot flash it dark.
uint8_t mode = 0;
if (!DpcdAccess(false, DPCD_EDP_BACKLIGHT_MODE, &mode, 1)) return false;
mode = (uint8_t)((mode & ~EDP_BLMODE_MASK) | EDP_BLMODE_DPCD);
if (!DpcdAccess(true, DPCD_EDP_BACKLIGHT_MODE, &mode, 1)) return false;
g_dpcdModePending = false;
return true;
}
// ---- GMBUS (HDMI/DVI DDC) -----------------------------------------------
static int GmbusPinForPort(int port) {
if (port < 0) return -1;
switch (g_pch) {
case PchFamily::Icp: {
// Combo PHY ports use pins 1-3; Type-C ports start at pin 9.
// Gen 11 numbers its first Type-C port C, Gen 12 numbers it D.
int firstTc = g_gpuGen >= 12 ? 3 : 2;
if (port < firstTc) return port + 1;
int pin = 9 + (port - firstTc);
return pin <= 14 ? pin : -1;
}
case PchFamily::Cnp:
return port == 1 ? 1 : port == 2 ? 2 : port == 3 ? 4 : -1;
default:
return port == 1 ? 5 : port == 2 ? 4 : port == 3 ? 6 : -1;
}
}
static bool GmbusWait(uint32_t bit) {
uint64_t start = Timekeeping::GetMilliseconds();
for (;;) {
uint32_t status = ReadReg(GMBUS2);
if (status & GMBUS_SATOER) return false;
if (status & bit) return true;
if (Timekeeping::GetMilliseconds() - start > 50) return false;
asm volatile("pause");
}
}
static bool GmbusWaitIdle() {
uint64_t start = Timekeeping::GetMilliseconds();
while (ReadReg(GMBUS2) & GMBUS_ACTIVE) {
if (Timekeeping::GetMilliseconds() - start > 50) return false;
asm volatile("pause");
}
return true;
}
// One complete I2C message (START .. STOP) at 100 kHz.
static bool GmbusTransfer(int pin, uint8_t address, bool read,
uint8_t* data, int size) {
if (pin < 0 || size <= 0 || size > 256) return false;
uint32_t gate = 0;
if (g_pch == PchFamily::Cnp) {
// Cannon Point drops GMBUS transfers while the unit clock gates.
gate = ReadReg(SOUTH_DSPCLK_GATE_D);
WriteReg(SOUTH_DSPCLK_GATE_D, gate | PCH_GMBUSUNIT_CLOCK_GATE_DISABLE);
}
WriteReg(GMBUS0, (uint32_t)pin);
uint32_t command = GMBUS_SW_RDY | GMBUS_CYCLE_WAIT
| ((uint32_t)size << GMBUS_BYTE_COUNT_SHIFT)
| ((uint32_t)address << 1) | (read ? GMBUS_SLAVE_READ : 0);
bool ok = true;
int pos = 0;
if (read) {
WriteReg(GMBUS1, command);
while (ok && pos < size) {
ok = GmbusWait(GMBUS_HW_RDY);
if (!ok) break;
uint32_t value = ReadReg(GMBUS3);
for (int i = 0; i < 4 && pos < size; i++, pos++)
data[pos] = (uint8_t)(value >> (8 * i));
}
} else {
// The first dword is loaded before the command starts the cycle.
uint32_t value = 0;
for (int i = 0; i < 4 && pos < size; i++, pos++)
value |= (uint32_t)data[pos] << (8 * i);
WriteReg(GMBUS3, value);
WriteReg(GMBUS1, command);
while (ok && pos < size) {
ok = GmbusWait(GMBUS_HW_RDY);
if (!ok) break;
value = 0;
for (int i = 0; i < 4 && pos < size; i++, pos++)
value |= (uint32_t)data[pos] << (8 * i);
WriteReg(GMBUS3, value);
}
}
KernelLogStream(INFO, "IntelGPU") << "Panel PWM "
<< (g_pwmSupported ? "available" : "not exposed");
if (ok) ok = GmbusWait(GMBUS_HW_WAIT_PHASE);
if (ok) {
WriteReg(GMBUS1, GMBUS_SW_RDY | GMBUS_CYCLE_STOP);
ok = GmbusWaitIdle();
} else {
// NAK or timeout: clear the error, let the bus go idle.
WriteReg(GMBUS1, GMBUS_SW_CLR_INT);
(void)GmbusWaitIdle();
WriteReg(GMBUS1, 0);
}
WriteReg(GMBUS0, 0);
if (g_pch == PchFamily::Cnp) WriteReg(SOUTH_DSPCLK_GATE_D, gate);
return ok;
}
// ---- DDC/CI (external monitors) -----------------------------------------
static bool DdcUsesAux() {
return g_connectorType == montauk::abi::DISPLAY_CONNECTOR_DP;
}
static bool DdcWrite(uint8_t* data, int size) {
if (DdcUsesAux()) {
if (size > 16) return false;
bool ok = AuxI2cTransfer(0x4, data, size, nullptr, 0, DdcAddress);
// Address-only write without MOT ends the I2C transaction.
bool stopped = AuxI2cTransfer(0x0, nullptr, 0, nullptr, 0, DdcAddress);
return ok && stopped;
}
return GmbusTransfer(GmbusPinForPort(g_ddiPort), DdcAddress, false, data, size);
}
static bool DdcRead(uint8_t* data, int size) {
if (DdcUsesAux()) {
if (size > 16) return false;
if (!AuxI2cTransfer(0x5, nullptr, 0, nullptr, 0, DdcAddress)) return false;
return AuxI2cTransfer(0x1, nullptr, 0, data, size, DdcAddress);
}
return GmbusTransfer(GmbusPinForPort(g_ddiPort), DdcAddress, true, data, size);
}
static void DdcPace() {
uint64_t now = Timekeeping::GetMilliseconds();
if (g_ddcLastMs != 0 && now - g_ddcLastMs < DdcCommandGapMs)
Timekeeping::Sleep(DdcCommandGapMs - (now - g_ddcLastMs));
}
// Send an MCCS command: host source address, length, payload, checksum.
// The checksum covers the monitor's 8-bit write address (0x6E) too.
static bool DdcCommand(const uint8_t* payload, int length) {
uint8_t packet[16];
if (length + 3 > (int)sizeof(packet)) return false;
packet[0] = DdcHostAddress;
packet[1] = (uint8_t)(0x80 | length);
uint8_t checksum = (uint8_t)(DdcAddress << 1) ^ packet[0] ^ packet[1];
for (int i = 0; i < length; i++) {
packet[2 + i] = payload[i];
checksum ^= payload[i];
}
packet[2 + length] = checksum;
DdcPace();
bool ok = DdcWrite(packet, length + 3);
g_ddcLastMs = Timekeeping::GetMilliseconds();
return ok;
}
static bool DdcGetBrightness(uint32_t& current, uint32_t& maximum) {
uint8_t request[2] = {0x01, DdcVcpBrightness}; // Get VCP Feature
if (!DdcCommand(request, 2)) return false;
Timekeeping::Sleep(DdcReplyDelayMs);
// Reply: 6E 88 02 rc 10 type maxH maxL curH curL checksum. The
// checksum covers the host's virtual address 0x50.
uint8_t reply[11] = {};
bool ok = DdcRead(reply, sizeof(reply));
g_ddcLastMs = Timekeeping::GetMilliseconds();
if (!ok) return false;
uint8_t checksum = 0x50;
for (int i = 0; i < 10; i++) checksum ^= reply[i];
if (checksum != reply[10] || reply[1] != 0x88 || reply[2] != 0x02
|| reply[3] != 0x00 || reply[4] != DdcVcpBrightness)
return false;
maximum = (uint32_t)reply[6] << 8 | reply[7];
current = (uint32_t)reply[8] << 8 | reply[9];
return maximum != 0;
}
static bool DdcSetBrightness(uint32_t value) {
uint8_t request[4] = {0x03, DdcVcpBrightness, // Set VCP Feature
(uint8_t)(value >> 8), (uint8_t)value};
return DdcCommand(request, 4);
}
// DDC/CI takes tens of milliseconds and needs the scheduler, so it is
// probed on first use from a syscall rather than during boot.
static bool EnsureDdc() {
if (g_blMethod == BacklightMethod::DdcCi) return true;
if (!g_ddcCandidate) return false;
uint64_t now = Timekeeping::GetMilliseconds();
if (g_ddcRetryMs != 0 && now < g_ddcRetryMs) return false;
for (int attempt = 0; attempt < 3; attempt++) {
uint32_t current = 0, maximum = 0;
if (!DdcGetBrightness(current, maximum)) continue;
g_blMethod = BacklightMethod::DdcCi;
g_blMax = maximum;
g_blPercent = RawToPercent(current, maximum);
KernelLogStream(OK, "IntelGPU") << "Monitor brightness via DDC/CI ("
<< base::dec << (uint64_t)current << "/" << (uint64_t)maximum << ")";
return true;
}
g_ddcRetryMs = Timekeeping::GetMilliseconds() + DdcRetryIntervalMs;
KernelLogStream(INFO, "IntelGPU") << "Monitor did not answer DDC/CI brightness query";
return false;
}
// ---- Discovery and dispatch ---------------------------------------------
static void DiscoverBacklight() {
g_blMethod = BacklightMethod::None;
g_blPercent = -1;
g_dpcdModePending = false;
g_ddcCandidate = false;
g_pch = DetectPch();
DetectEmbeddedDp();
bool panel = g_connectorType == montauk::abi::DISPLAY_CONNECTOR_EDP
|| g_connectorType == montauk::abi::DISPLAY_CONNECTOR_LVDS;
if (panel) {
// A panel firmware already put in DPCD mode ignores its PWM pin.
// Otherwise PWM is what firmware lit it with; take DPCD over only
// when there is no live PWM controller.
int dpcd = ProbeDpcdBacklight();
if (dpcd == 2) {
g_blMethod = BacklightMethod::DpcdAux;
} else if (!DiscoverPwmBacklight() && dpcd == 1) {
g_blMethod = BacklightMethod::DpcdAux;
g_dpcdModePending = true;
g_blPercent = 100;
}
} else if (g_gpuGen >= 8 && g_ddiPort >= 0
&& (g_connectorType == montauk::abi::DISPLAY_CONNECTOR_HDMI
|| g_connectorType == montauk::abi::DISPLAY_CONNECTOR_DVI
|| (g_connectorType == montauk::abi::DISPLAY_CONNECTOR_DP
&& g_auxControlReg != 0))) {
g_ddcCandidate = DdcUsesAux() || GmbusPinForPort(g_ddiPort) >= 0;
}
const char* pch = g_pch == PchFamily::Ibx ? "IBX" : g_pch == PchFamily::Lpt ? "LPT"
: g_pch == PchFamily::Cnp ? "CNP" : g_pch == PchFamily::Icp ? "ICP+"
: "unknown";
if (g_blMethod != BacklightMethod::None) {
KernelLogStream(INFO, "IntelGPU") << "Backlight: " << BrightnessMethod()
<< (g_dpcdModePending ? " (on first change)" : "")
<< ", PCH " << pch;
} else {
KernelLogStream(INFO, "IntelGPU") << "Backlight: "
<< (g_ddcCandidate ? "DDC/CI probe deferred" : "not exposed")
<< ", PCH " << pch;
}
}
static int BrightnessLocked(int percent) {
if (percent > 100) percent = 100;
switch (g_blMethod) {
case BacklightMethod::PwmIlk:
case BacklightMethod::PwmLpt:
case BacklightMethod::PwmCnp: {
uint32_t maximum = 0, duty = 0;
if (!PwmState(maximum, duty)) return -1;
if (percent >= 0) {
if (percent < MinPanelPercent) percent = MinPanelPercent;
uint32_t raw = PercentToRaw(percent, maximum);
PwmWriteDuty(raw ? raw : 1);
if (!PwmState(maximum, duty)) return -2;
}
g_blPercent = RawToPercent(duty, maximum);
return g_blPercent;
}
case BacklightMethod::DpcdAux: {
if (percent < 0) {
if (g_dpcdModePending) return g_blPercent;
int level = DpcdReadPercent();
if (level >= 0) g_blPercent = level;
return g_blPercent;
}
if (percent < MinPanelPercent) percent = MinPanelPercent;
if (!DpcdWritePercent(percent)) return -2;
int level = DpcdReadPercent();
g_blPercent = level >= 0 ? level : percent;
return g_blPercent;
}
case BacklightMethod::None:
if (!EnsureDdc()) return -1;
[[fallthrough]];
case BacklightMethod::DdcCi: {
// Monitors answer queries slowly, and the OSD is the only
// other writer, so the last known level stands in.
if (percent < 0) return g_blPercent;
if (!DdcSetBrightness(PercentToRaw(percent, g_blMax))) return -2;
g_blPercent = percent;
return g_blPercent;
}
}
return -1;
}
static void InitializeDisplayManagement() {
@@ -942,7 +1483,7 @@ namespace Drivers::Graphics::IntelGPU {
DiscoverActiveTranscoder();
ParseEdid();
SelectCurrentMode();
DiscoverPwm();
DiscoverBacklight();
}
// =========================================================================
@@ -1660,7 +2201,6 @@ namespace Drivers::Graphics::IntelGPU {
out->capabilities = montauk::abi::DISPLAY_CAP_MODESET;
if (g_edidValid) out->capabilities |= montauk::abi::DISPLAY_CAP_EDID;
if (g_pwmSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_BRIGHTNESS;
if (g_flipSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_PAGE_FLIP;
for (int i = 0; i < g_modeCount; i++) {
if (i != g_currentMode
@@ -1672,6 +2212,7 @@ namespace Drivers::Graphics::IntelGPU {
out->modeCount = (uint32_t)g_modeCount;
out->currentMode = g_currentMode;
out->brightness = Brightness(-1);
if (out->brightness >= 0) out->capabilities |= montauk::abi::DISPLAY_CAP_BRIGHTNESS;
out->deviceId = g_gpuInfo.deviceId;
out->generation = g_gpuInfo.gen;
out->connectorType = g_connectorType;
@@ -1737,20 +2278,28 @@ namespace Drivers::Graphics::IntelGPU {
}
int Brightness(int percent) {
if (!g_initialized || !g_pwmSupported) return -1;
uint32_t value = ReadReg(g_pwmCtlReg);
uint32_t maximum = value >> 16;
if (maximum == 0) return -1;
if (percent < 0) {
uint32_t duty = value & 0xFFFF;
if (duty > maximum) duty = maximum;
return (int)(((uint64_t)duty * 100 + maximum / 2) / maximum);
if (!g_initialized) return -1;
if (g_blMethod == BacklightMethod::None && !g_ddcCandidate) return -1;
g_blLock.Acquire();
int result = BrightnessLocked(percent);
g_blLock.Release();
return result;
}
uint8_t ActiveConnectorType() {
return g_connectorType;
}
const char* BrightnessMethod() {
switch (g_blMethod) {
case BacklightMethod::PwmIlk:
case BacklightMethod::PwmLpt:
case BacklightMethod::PwmCnp: return "PWM";
case BacklightMethod::DpcdAux: return "eDP AUX";
case BacklightMethod::DdcCi: return "DDC/CI";
case BacklightMethod::None: break;
}
if (percent > 100) percent = 100;
uint32_t duty = (uint32_t)(((uint64_t)maximum * (uint32_t)percent + 50) / 100);
WriteReg(g_pwmCtlReg, (value & 0xFFFF0000u) | duty);
(void)ReadReg(g_pwmCtlReg);
return Brightness(-1);
return nullptr;
}
// =========================================================================
@@ -1993,6 +2542,12 @@ namespace Drivers::Graphics::IntelGPU {
<< base::dec << (uint64_t)g_frontBuffer << ")";
}
// 7. Firmware POST resets the PWM duty to its own default. Put back
// the level the user chose (register writes only; no AUX here).
uint32_t pwmMax = 0, pwmDuty = 0;
if (g_blPercent > 0 && PwmState(pwmMax, pwmDuty))
PwmWriteDuty(PercentToRaw(g_blPercent, pwmMax));
KernelLogStream(OK, "IntelGPU") << "Display restored after S3 resume";
}
+55 -10
View File
@@ -237,6 +237,7 @@ namespace Drivers::Graphics::IntelGPU {
static constexpr uint32_t DVOB = 0x61140; // DVO-B
static constexpr uint32_t DVOC = 0x61160; // DVO-C
static constexpr uint32_t LVDS = 0x61180; // LVDS panel
static constexpr uint32_t DP_A = 0x64000; // CPU eDP (ILK-IVB)
static constexpr uint32_t DP_B = 0x64100; // DisplayPort B
static constexpr uint32_t DP_C = 0x64200; // DisplayPort C
static constexpr uint32_t DP_D = 0x64300; // DisplayPort D
@@ -277,12 +278,35 @@ namespace Drivers::Graphics::IntelGPU {
static constexpr uint32_t DP_AUX_MESSAGE_SHIFT = 20;
// PCH/CPU panel PWM. Firmware owns the frequency; the driver only changes
// the duty field and preserves enable/polarity/reserved bits.
// the duty field and preserves enable/polarity/reserved bits. Three
// layouts share these addresses:
// Ibex Peak..Panther Point (ILK-IVB): period = PCH_CTL2[31:16], duty
// = CPU_CTL[15:0].
// Lynx Point..Kaby Point (HSW-KBL): PCH_CTL2 = period << 16 | duty.
// Cannon Point and later (and BXT/GLK): CTL1 enable, then full 32-bit
// FREQ and DUTY registers, per controller at +0x100. Reading these as
// the LPT layout corrupts the period, so the PCH must be known.
static constexpr uint32_t BLC_PWM_CPU_CTL2 = 0x48250;
static constexpr uint32_t BLC_PWM_CPU_CTL = 0x48254;
static constexpr uint32_t BLC_PWM_PCH_CTL1 = 0xC8250;
static constexpr uint32_t BLC_PWM_PCH_CTL2 = 0xC8254;
static constexpr uint32_t BLC_PWM_ENABLE = (1u << 31);
static constexpr uint32_t BXT_BLC_PWM_CTL1 = 0xC8250;
static constexpr uint32_t BXT_BLC_PWM_FREQ1 = 0xC8254;
static constexpr uint32_t BXT_BLC_PWM_DUTY1 = 0xC8258;
static constexpr uint32_t BXT_BLC_PWM_CONTROLLER_DELTA = 0x100;
// eDP DPCD backlight (VESA eDP 1.2+ "AUX backlight control").
static constexpr uint32_t DPCD_EDP_REV = 0x700;
static constexpr uint32_t DPCD_EDP_GENERAL_CAP_1 = 0x701;
static constexpr uint32_t DPCD_EDP_BACKLIGHT_CAP = 0x702;
static constexpr uint32_t DPCD_EDP_BACKLIGHT_MODE = 0x721;
static constexpr uint32_t DPCD_EDP_BRIGHTNESS_MSB = 0x722;
static constexpr uint8_t EDP_CAP1_AUX_ENABLE = (1u << 2);
static constexpr uint8_t EDP_BLCAP_AUX_SET = (1u << 1);
static constexpr uint8_t EDP_BLCAP_BYTE_COUNT_16 = (1u << 2);
static constexpr uint8_t EDP_BLMODE_MASK = 0x3;
static constexpr uint8_t EDP_BLMODE_DPCD = 0x2;
// ADPA bits
static constexpr uint32_t ADPA_DAC_ENABLE = (1u << 31);
@@ -294,13 +318,25 @@ namespace Drivers::Graphics::IntelGPU {
static constexpr uint32_t LVDS_PORT_ENABLE = (1u << 31);
static constexpr uint32_t LVDS_PIPE_B_SELECT = (1u << 30);
// --- GMBUS (I2C for EDID) ---
static constexpr uint32_t GMBUS0 = 0x5100; // Clock/port select
static constexpr uint32_t GMBUS1 = 0x5104; // Command/status
static constexpr uint32_t GMBUS2 = 0x5108; // Status
static constexpr uint32_t GMBUS3 = 0x510C; // Data buffer
static constexpr uint32_t GMBUS4 = 0x5110; // Interrupt mask
static constexpr uint32_t GMBUS5 = 0x5120; // 2-byte index
// --- GMBUS (display I2C: DDC for HDMI/DVI) ---
// Every supported generation is PCH-split, which moves GMBUS from the
// legacy 0x5100 block into the PCH display range.
static constexpr uint32_t GMBUS0 = 0xC5100; // Clock/pin select
static constexpr uint32_t GMBUS1 = 0xC5104; // Command/status
static constexpr uint32_t GMBUS2 = 0xC5108; // Status
static constexpr uint32_t GMBUS3 = 0xC510C; // Data buffer
static constexpr uint32_t GMBUS4 = 0xC5110; // Interrupt mask
static constexpr uint32_t GMBUS5 = 0xC5120; // 2-byte index
static constexpr uint32_t GMBUS_SW_CLR_INT = (1u << 31);
static constexpr uint32_t GMBUS_SW_RDY = (1u << 30);
static constexpr uint32_t GMBUS_CYCLE_WAIT = (1u << 25);
static constexpr uint32_t GMBUS_CYCLE_STOP = (4u << 25);
static constexpr uint32_t GMBUS_BYTE_COUNT_SHIFT = 16;
static constexpr uint32_t GMBUS_SLAVE_READ = 1u;
static constexpr uint32_t GMBUS_HW_WAIT_PHASE = (1u << 14);
static constexpr uint32_t GMBUS_HW_RDY = (1u << 11);
static constexpr uint32_t GMBUS_SATOER = (1u << 10); // NAK
static constexpr uint32_t GMBUS_ACTIVE = (1u << 9);
// --- Hardware status page ---
static constexpr uint32_t HWS_PGA = 0x02080;
@@ -496,10 +532,19 @@ namespace Drivers::Graphics::IntelGPU {
int SetDisplayMode(int modeIndex);
// percent < 0 queries the current brightness. Otherwise sets 0..100 and
// returns the resulting percentage. Returns -1 when no PWM backlight is
// exposed by the active output.
// returns the resulting percentage. Returns -1 when the active output has
// no controllable backlight, -2 when the hardware rejected the change.
// Backends, in order: panel PWM, eDP DPCD (AUX) backlight, and DDC/CI
// (MCCS VCP 0x10) for an external monitor on HDMI/DVI/DP. May sleep.
int Brightness(int percent);
// DisplayConnectorType of the output the driver adopted from firmware.
uint8_t ActiveConnectorType();
// Short name of the backlight method in use ("PWM", "eDP AUX", "DDC/CI"),
// or nullptr when there is none.
const char* BrightnessMethod();
// =========================================================================
// Page flipping (double-buffered scanout)
// =========================================================================
+1
View File
@@ -32,6 +32,7 @@ namespace Sched::Consent {
{"set_time", CAP_SET_TIME},
{"display_admin", CAP_DISPLAY_ADMIN},
{"log_read", CAP_LOG_READ},
{"brightness", CAP_BRIGHTNESS},
};
constexpr uint64_t EligibleMask() {
+6 -3
View File
@@ -3,7 +3,8 @@
#
# Capability names: process_admin, power_request, power_control, suspend,
# storage_admin, raw_storage, network_admin, set_time, user_admin,
# display_admin, device_admin, log_read, system_image, compositor and "all".
# display_admin, device_admin, log_read, system_image, compositor, brightness
# and "all".
#
# ==== System services (started by init) ====
@@ -26,6 +27,7 @@ delegable = [
"power_request", "suspend", "process_admin", "storage_admin",
"raw_storage", "network_admin", "set_time", "user_admin",
"display_admin", "device_admin", "log_read", "compositor",
"brightness",
]
[grant.sshd]
@@ -38,11 +40,12 @@ effective = [
"power_request", "suspend", "process_admin", "storage_admin",
"raw_storage", "network_admin", "set_time", "user_admin",
"display_admin", "device_admin", "log_read", "compositor",
"brightness",
]
delegable = [
"power_request", "suspend", "process_admin", "storage_admin",
"raw_storage", "network_admin", "set_time", "user_admin",
"display_admin", "device_admin", "log_read",
"display_admin", "device_admin", "log_read", "brightness",
]
[grant.terminal]
@@ -68,7 +71,7 @@ delegable = ["network_admin"]
[grant.display]
path = "0:/apps/display/display.elf"
effective = ["display_admin"]
effective = ["display_admin", "brightness"]
[grant.disks]
path = "0:/apps/disks/disks.elf"
+9 -3
View File
@@ -298,8 +298,14 @@ namespace montauk::abi {
never part of a session mask, never part of "all", and never
delegated by the compositor to what it launches. */
static constexpr uint64_t CAP_COMPOSITOR = 1ULL << 13;
static constexpr uint64_t CAP_ALL = (1ULL << 14) - 1;
static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND;
/* Set the backlight of the active display (SYS_DISPLAYBRIGHTNESS).
A seat preference rather than display administration, so every
session holds it; CAP_DISPLAY_ADMIN keeps mode setting. Reading the
level needs nothing. */
static constexpr uint64_t CAP_BRIGHTNESS = 1ULL << 14;
static constexpr uint64_t CAP_ALL = (1ULL << 15) - 1;
static constexpr uint64_t CAP_STANDARD_SESSION =
CAP_POWER_REQUEST | CAP_SUSPEND | CAP_BRIGHTNESS;
static constexpr uint64_t CAP_ADMIN_SESSION =
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
@@ -351,7 +357,7 @@ namespace montauk::abi {
deferred delegation, not amplification. */
static constexpr uint64_t CAP_CONSENT_ELIGIBLE =
CAP_STORAGE_ADMIN | CAP_NETWORK_ADMIN | CAP_SET_TIME |
CAP_DISPLAY_ADMIN | CAP_LOG_READ;
CAP_DISPLAY_ADMIN | CAP_LOG_READ | CAP_BRIGHTNESS;
static_assert((CAP_CONSENT_ELIGIBLE & ~CAP_ADMIN_SESSION) == 0,
"consent can only hand out authority an admin session holds");
+12
View File
@@ -227,6 +227,18 @@ struct DesktopState {
montauk::abi::BatteryInfo battery_info;
uint64_t battery_last_poll;
// Display brightness. Present only while the active output has a
// controllable backlight (laptop panel, or a monitor answering DDC/CI).
SvgIcon icon_brightness;
bool brightness_present;
bool brightness_popup_open;
Rect brightness_icon_rect;
int brightness_level; // 0-100, as shown on the slider
bool brightness_dragging;
int brightness_pending; // drag sample not yet sent, -1 none
uint64_t brightness_last_send;
uint64_t brightness_last_poll;
bool vol_popup_open;
Rect vol_icon_rect;
int vol_level; // 0-100
+1
View File
@@ -38,6 +38,7 @@ namespace caps {
{"log_read", montauk::abi::CAP_LOG_READ},
{"system_image", montauk::abi::CAP_SYSTEM_IMAGE},
{"compositor", montauk::abi::CAP_COMPOSITOR},
{"brightness", montauk::abi::CAP_BRIGHTNESS},
};
// Never implied by "all": each must be named explicitly in a grant.
+3 -1
View File
@@ -31,10 +31,12 @@ namespace consent {
"0:/os/icons/network-wired.svg"},
{montauk::abi::CAP_SET_TIME, "change the system clock and time zone",
"0:/os/icons/preferences-system-time.svg"},
{montauk::abi::CAP_DISPLAY_ADMIN, "change the screen resolution and brightness",
{montauk::abi::CAP_DISPLAY_ADMIN, "change the screen resolution",
"0:/os/icons/preferences-desktop-display.svg"},
{montauk::abi::CAP_LOG_READ, "read the system log, which can reveal what other apps and devices are doing",
"0:/os/icons/utilities-log-viewer.svg"},
{montauk::abi::CAP_BRIGHTNESS, "change the screen brightness",
"0:/os/icons/preferences-desktop-display.svg"},
};
// Fallback for a capability without its own icon.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -2
View File
@@ -20,7 +20,7 @@ extern "C" {
using namespace gui;
static constexpr int WIN_W = 760;
static constexpr int WIN_H = 560;
static constexpr int WIN_H = 582; // one grid row per capability
static constexpr int TAB_H = 36;
static constexpr int FOOTER_H = 44;
static constexpr int PAD = 18;
@@ -60,11 +60,12 @@ static const char* const kCapDescriptions[CAP_COUNT] = {
"Configure network interfaces, DHCP and Wi-Fi",
"Set the system clock",
"Manage user accounts and session identities",
"Change display modes and panel brightness",
"Change display modes",
"Manage hardware devices (Bluetooth, radio)",
"Read the kernel log",
"Write to 0:/os and 0:/apps (never part of \"all\")",
"Read and send input to other windows (never part of \"all\")",
"Set the display backlight",
};
// ==== Grant table model ====
+1 -1
View File
@@ -56,7 +56,7 @@ LDFLAGS := \
# ---- C++ source files ----
CORE_SRCS := main.cpp window.cpp panel.cpp wifi.cpp battery.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp desktop_catalog.cpp consent.cpp font_data.cpp stb_truetype_impl.cpp
CORE_SRCS := main.cpp window.cpp panel.cpp wifi.cpp battery.cpp brightness.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp desktop_catalog.cpp consent.cpp font_data.cpp stb_truetype_impl.cpp
APP_SRCS := $(sort $(shell find apps -name '*.cpp' -print))
SRCS := $(CORE_SRCS) $(APP_SRCS)
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
+263
View File
@@ -0,0 +1,263 @@
/*
* brightness.cpp
* Panel brightness control: the icon that appears when the active display
* has a controllable backlight (laptop panel or DDC/CI monitor), and the
* slider popout that drives it.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "desktop_internal.hpp"
#include <gui/mtk.hpp>
using namespace gui;
// ============================================================================
// Geometry
// ============================================================================
static constexpr int BRIGHT_POPUP_W = 220;
static constexpr int BRIGHT_SLIDER_INSET = 16;
static constexpr int BRIGHT_SLIDER_H = 8;
static constexpr int BRIGHT_KNOB_R = 8;
static constexpr int BRIGHT_STEP = 5; // [-]/[+] and one wheel notch
static constexpr int BRIGHT_MIN = 5; // keep the screen readable
static constexpr int BRIGHT_POLL_MS = 2000; // follow the Display app
static constexpr int BRIGHT_ABSENT_POLL_MS = 30000;
// A DDC/CI monitor needs ~50ms per command, so a drag is sampled rather than
// forwarding every mouse move; the release always sends the final level.
static constexpr int BRIGHT_DRAG_SEND_MS = 100;
static const char* DISPLAY_APP_PATH = "0:/apps/display/display.elf";
static int brightness_popup_x(const DesktopState* ds) {
int x = ds->brightness_icon_rect.x + ds->brightness_icon_rect.w - BRIGHT_POPUP_W;
return x < 4 ? 4 : x;
}
static int brightness_header_y(const DesktopState* ds) {
(void)ds;
return PANEL_HEIGHT + 2 + 12;
}
static Rect brightness_slider_rect(const DesktopState* ds) {
int y = brightness_header_y(ds) + system_font_height() + 18;
return {brightness_popup_x(ds) + BRIGHT_SLIDER_INSET, y,
BRIGHT_POPUP_W - 2 * BRIGHT_SLIDER_INSET, BRIGHT_SLIDER_H};
}
// [-] [+] row under the slider.
static Rect brightness_minus_rect(const DesktopState* ds) {
Rect s = brightness_slider_rect(ds);
return {brightness_popup_x(ds) + (BRIGHT_POPUP_W - (36 * 2 + 8)) / 2,
s.y + s.h + 16, 36, 24};
}
static Rect brightness_plus_rect(const DesktopState* ds) {
Rect m = brightness_minus_rect(ds);
return {m.x + m.w + 8, m.y, m.w, m.h};
}
static Rect brightness_settings_rect(const DesktopState* ds) {
Rect m = brightness_minus_rect(ds);
return {brightness_popup_x(ds) + 8, m.y + m.h + 12,
BRIGHT_POPUP_W - 16, system_font_height() + 10};
}
static Rect brightness_popup_rect(const DesktopState* ds) {
Rect settings = brightness_settings_rect(ds);
int y = PANEL_HEIGHT + 2;
return {brightness_popup_x(ds), y, BRIGHT_POPUP_W, settings.y + settings.h + 8 - y};
}
// ============================================================================
// Hardware access
// ============================================================================
static int clamp_level(int level) {
if (level < BRIGHT_MIN) return BRIGHT_MIN;
if (level > 100) return 100;
return level;
}
// Send `level` now. The kernel reports what the hardware ended up at, which
// for a panel with coarse steps is not always exactly what was asked.
static void brightness_apply(DesktopState* ds, int level) {
ds->brightness_pending = -1;
ds->brightness_last_send = montauk::get_milliseconds();
int result = montauk::display_brightness(level);
if (result >= 0 && !ds->brightness_dragging) ds->brightness_level = result;
}
// While dragging, the slider tracks the pointer immediately and the hardware
// catches up at most every BRIGHT_DRAG_SEND_MS.
static void brightness_request(DesktopState* ds, int level) {
level = clamp_level(level);
ds->brightness_level = level;
uint64_t now = montauk::get_milliseconds();
if (ds->brightness_dragging && now - ds->brightness_last_send < BRIGHT_DRAG_SEND_MS) {
ds->brightness_pending = level;
return;
}
brightness_apply(ds, level);
}
static int brightness_from_x(const DesktopState* ds, int mx) {
Rect s = brightness_slider_rect(ds);
int v = ((mx - s.x) * 100) / s.w;
return clamp_level(v);
}
// ============================================================================
// Startup and polling
// ============================================================================
void desktop_brightness_init(DesktopState* ds) {
ds->icon_brightness = svg_load("0:/os/icons/display-brightness-symbolic.svg",
16, 16, colors::PANEL_TEXT);
ds->brightness_popup_open = false;
ds->brightness_icon_rect = {0, 0, 0, 0};
ds->brightness_dragging = false;
ds->brightness_pending = -1;
ds->brightness_last_send = 0;
ds->brightness_last_poll = montauk::get_milliseconds();
int level = montauk::display_brightness(-1);
ds->brightness_present = level >= 0;
ds->brightness_level = level >= 0 ? level : 100;
}
bool desktop_brightness_poll(DesktopState* ds, uint64_t now) {
// Flush a drag sample that was held back by the send throttle.
if (ds->brightness_pending >= 0 && now - ds->brightness_last_send >= BRIGHT_DRAG_SEND_MS)
brightness_apply(ds, ds->brightness_pending);
if (ds->brightness_dragging) return false;
// An absent backlight is re-checked now and then: an external monitor
// can be switched to DDC/CI in its menu, and the kernel retries that.
uint64_t interval = ds->brightness_present ? BRIGHT_POLL_MS : BRIGHT_ABSENT_POLL_MS;
if (now - ds->brightness_last_poll < interval) return false;
ds->brightness_last_poll = now;
int level = montauk::display_brightness(-1);
bool present = level >= 0;
bool changed = present != ds->brightness_present;
ds->brightness_present = present;
if (!present) {
if (changed) ds->brightness_popup_open = false;
return changed;
}
if (level != ds->brightness_level) {
ds->brightness_level = level;
changed = true;
}
return changed;
}
bool desktop_brightness_dragging(const DesktopState* ds) {
return ds->brightness_dragging;
}
// ============================================================================
// Popout
// ============================================================================
static void draw_button(DesktopState* ds, const Rect& r, const char* label) {
Framebuffer& fb = ds->fb;
Color bg = r.contains(ds->mouse.x, ds->mouse.y) ? Color::from_rgb(0xD0, 0xD0, 0xD0)
: Color::from_rgb(0xE0, 0xE0, 0xE0);
fill_rounded_rect(fb, r.x, r.y, r.w, r.h, 6, bg);
int tw = text_width(label);
draw_text(fb, r.x + (r.w - tw) / 2, r.y + (r.h - system_font_height()) / 2,
label, colors::TEXT_COLOR);
}
void desktop_draw_brightness_popup(DesktopState* ds) {
Framebuffer& fb = ds->fb;
Rect popup = brightness_popup_rect(ds);
Color accent = ds->settings.accent_color;
draw_shadow(fb, popup.x, popup.y, popup.w, popup.h, 4, colors::SHADOW);
fill_rounded_rect(fb, popup.x, popup.y, popup.w, popup.h, 8, colors::MENU_BG);
draw_rect(fb, popup.x, popup.y, popup.w, popup.h, colors::BORDER);
int ty = brightness_header_y(ds);
draw_text(fb, popup.x + 14, ty, "Brightness", colors::TEXT_COLOR);
char value[8];
snprintf(value, sizeof(value), "%d%%", ds->brightness_level);
draw_text(fb, popup.x + popup.w - 14 - text_width(value), ty, value, accent);
Rect s = brightness_slider_rect(ds);
fill_rounded_rect(fb, s.x, s.y, s.w, s.h, 4, Color::from_rgb(0xDD, 0xDD, 0xDD));
int fill_w = (ds->brightness_level * s.w) / 100;
if (fill_w > 0) fill_rounded_rect(fb, s.x, s.y, fill_w, s.h, 4, accent);
int knob_cx = s.x + fill_w;
int knob_cy = s.y + s.h / 2;
fill_circle(fb, knob_cx, knob_cy, BRIGHT_KNOB_R, accent);
fill_circle(fb, knob_cx, knob_cy, BRIGHT_KNOB_R - 3, colors::WHITE);
draw_button(ds, brightness_minus_rect(ds), "-");
draw_button(ds, brightness_plus_rect(ds), "+");
Rect settings = brightness_settings_rect(ds);
if (settings.contains(ds->mouse.x, ds->mouse.y))
fill_rounded_rect(fb, settings.x, settings.y, settings.w, settings.h, 5,
Color::from_rgb(0xF0, 0xF3, 0xF6));
draw_text(fb, settings.x + 6, settings.y + (settings.h - system_font_height()) / 2,
"Display Settings...", colors::TEXT_COLOR);
}
// Returns true when the event was consumed.
bool desktop_brightness_handle_mouse(DesktopState* ds, int mx, int my, bool left_pressed,
bool left_held, bool left_released, int scroll) {
if (!ds->brightness_present) return false;
if (ds->brightness_dragging) {
if (left_held) {
brightness_request(ds, brightness_from_x(ds, mx));
return true;
}
if (left_released) {
ds->brightness_dragging = false;
brightness_apply(ds, ds->brightness_level);
return true;
}
}
// The wheel works over the panel icon too, without opening anything.
bool over_popup = ds->brightness_popup_open && brightness_popup_rect(ds).contains(mx, my);
if (scroll != 0 && (over_popup || ds->brightness_icon_rect.contains(mx, my))) {
brightness_request(ds, ds->brightness_level + scroll * BRIGHT_STEP);
return true;
}
if (!ds->brightness_popup_open || !left_pressed) return false;
if (!over_popup) {
if (!ds->brightness_icon_rect.contains(mx, my)) ds->brightness_popup_open = false;
return false;
}
Rect s = brightness_slider_rect(ds);
if (my >= s.y - 10 && my <= s.y + s.h + 10
&& mx >= s.x - BRIGHT_KNOB_R && mx <= s.x + s.w + BRIGHT_KNOB_R) {
ds->brightness_dragging = true;
brightness_request(ds, brightness_from_x(ds, mx));
return true;
}
if (brightness_minus_rect(ds).contains(mx, my)) {
brightness_request(ds, ds->brightness_level - BRIGHT_STEP);
return true;
}
if (brightness_plus_rect(ds).contains(mx, my)) {
brightness_request(ds, ds->brightness_level + BRIGHT_STEP);
return true;
}
if (brightness_settings_rect(ds).contains(mx, my)) {
ds->brightness_popup_open = false;
desktop_spawn_app(ds, DISPLAY_APP_PATH, nullptr);
return true;
}
return true; // inside the popup, on nothing in particular
}
+4
View File
@@ -273,6 +273,10 @@ void gui::desktop_compose(DesktopState* ds) {
desktop_draw_battery_popup(ds);
}
if (ds->brightness_popup_open) {
desktop_draw_brightness_popup(ds);
}
// Draw volume popup if open
if (ds->vol_popup_open) {
desktop_draw_vol_popup(ds);
+1
View File
@@ -68,6 +68,7 @@ void desktop_lock_screen(DesktopState* ds) {
ds->net_popup_open = false;
ds->wifi_popup_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
// Cache display name for lock screen rendering
montauk::strncpy(ds->lock_display_name, ds->current_user, sizeof(ds->lock_display_name));
@@ -205,6 +205,15 @@ bool desktop_battery_poll(gui::DesktopState* ds, uint64_t now);
void desktop_draw_battery_popup(gui::DesktopState* ds);
bool desktop_battery_handle_mouse(gui::DesktopState* ds, int mx, int my);
// brightness.cpp
void desktop_brightness_init(gui::DesktopState* ds);
bool desktop_brightness_poll(gui::DesktopState* ds, uint64_t now);
bool desktop_brightness_dragging(const gui::DesktopState* ds);
void desktop_draw_brightness_popup(gui::DesktopState* ds);
bool desktop_brightness_handle_mouse(gui::DesktopState* ds, int mx, int my,
bool left_pressed, bool left_held,
bool left_released, int scroll);
// compose.cpp
void desktop_draw_lock_screen(gui::DesktopState* ds);
void desktop_mark_background_dirty(gui::DesktopState* ds);
+28
View File
@@ -187,6 +187,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->app_menu_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
ds->net_popup_open = false;
ds->wifi_popup_open = false;
ds->battery_popup_open = false;
@@ -518,11 +519,17 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
return; // click inside popup but not on any control
} else if (!ds->vol_icon_rect.contains(mx, my)) {
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
ds->vol_dragging = false;
}
}
}
// Brightness popup, a drag on its slider, or the wheel over its icon.
if (desktop_brightness_handle_mouse(ds, mx, my, left_pressed, left_held,
left_released, ev.scroll))
return;
// Wi-Fi popup. The passphrase dialog is an ordinary window and needs
// nothing here.
if (ds->wifi_popup_open) {
@@ -566,6 +573,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->wifi_popup_open = false;
ds->battery_popup_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
return;
}
@@ -583,6 +591,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->wifi_popup_open = false;
ds->battery_popup_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
return;
}
@@ -591,6 +600,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
if (ds->vol_icon_rect.w > 0 && ds->vol_icon_rect.contains(mx, my)) {
ds->vol_popup_open = !ds->vol_popup_open;
ds->vol_dragging = false;
ds->brightness_popup_open = false;
ds->app_menu_open = false;
ds->net_popup_open = false;
ds->wifi_popup_open = false;
@@ -599,11 +609,25 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
return;
}
// Brightness icon
if (ds->brightness_present && ds->brightness_icon_rect.w > 0
&& ds->brightness_icon_rect.contains(mx, my)) {
ds->brightness_popup_open = !ds->brightness_popup_open;
ds->app_menu_open = false;
ds->vol_popup_open = false;
ds->net_popup_open = false;
ds->wifi_popup_open = false;
ds->battery_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
return;
}
// Network icon
if (ds->net_icon_rect.w > 0 && ds->net_icon_rect.contains(mx, my)) {
ds->net_popup_open = !ds->net_popup_open;
ds->app_menu_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
ds->wifi_popup_open = false;
ds->battery_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
@@ -615,6 +639,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->wifi_popup_open = !ds->wifi_popup_open;
ds->app_menu_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
ds->net_popup_open = false;
ds->battery_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
@@ -626,6 +651,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->battery_popup_open = !ds->battery_popup_open;
ds->app_menu_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
ds->net_popup_open = false;
ds->wifi_popup_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
@@ -783,6 +809,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->app_menu_open = false;
gui::mtk::context_menu_close(ds->ctx_menu);
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
}
// Forward continuous mouse events to focused window (hover, drag, release).
@@ -861,6 +888,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ds->wifi_popup_open = false;
ds->battery_popup_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
}
}
}
+1
View File
@@ -408,6 +408,7 @@ void desktop_open_launcher(DesktopState* ds) {
ds->net_popup_open = false;
ds->wifi_popup_open = false;
ds->vol_popup_open = false;
ds->brightness_popup_open = false;
ds->vol_dragging = false;
desktop_update_launcher_results(ds);
}
+3
View File
@@ -393,6 +393,7 @@ void gui::desktop_init(DesktopState* ds) {
desktop_refresh_netifs(ds);
desktop_wifi_init(ds);
desktop_battery_init(ds);
desktop_brightness_init(ds);
desktop_consent_init(ds);
ds->vol_popup_open = false;
@@ -445,6 +446,7 @@ static bool desktop_has_visible_dirty_window(const DesktopState* ds) {
static bool desktop_has_active_interaction(const DesktopState* ds) {
if (ds->vol_dragging) return true;
if (desktop_brightness_dragging(ds)) return true;
for (int i = 0; i < ds->window_count; i++) {
const Window& win = ds->windows[i];
@@ -521,6 +523,7 @@ static bool desktop_refresh_panel_state(DesktopState* ds, uint64_t now) {
if (desktop_wifi_poll(ds, now)) changed = true;
if (desktop_battery_poll(ds, now)) changed = true;
if (desktop_brightness_poll(ds, now)) changed = true;
// Event-driven sync: the kernel mixer bumps a serial on every state
// change. Reading it is one cheap syscall; refresh only when the serial
+11
View File
@@ -173,6 +173,17 @@ void gui::desktop_draw_panel(DesktopState* ds) {
int icon_y = (PANEL_HEIGHT - 16) / 2;
int next_icon_x = vol_icon_x - 16 - 10;
// Brightness sits next to the speaker: the two slider controls together.
if (ds->brightness_present) {
ds->brightness_icon_rect = {next_icon_x, icon_y, 16, 16};
next_icon_x -= 16 + 10;
if (ds->icon_brightness.pixels)
fb.blit_alpha(ds->brightness_icon_rect.x, icon_y, ds->icon_brightness.width,
ds->icon_brightness.height, ds->icon_brightness.pixels);
} else {
ds->brightness_icon_rect = {0, 0, 0, 0};
}
if (ds->eth_present) {
ds->net_icon_rect = {next_icon_x, icon_y, 16, 16};
next_icon_x -= 16 + 10;
+1
View File
@@ -12,6 +12,7 @@
#include <montauk/heap.h>
#include <montauk/user.h>
#include <montauk/config.h>
#include <montauk/capabilities.h>
#include <gui/gui.hpp>
#include <gui/framebuffer.hpp>
#include <gui/draw.hpp>
+4
View File
@@ -52,6 +52,10 @@ void launch_session(LoginState* ls) {
caps.effective = montauk::user::is_admin(ls->username)
? montauk::abi::CAP_ADMIN_SESSION
: montauk::abi::CAP_STANDARD_SESSION;
// Clamp to what this login may delegate: a grant table from before a
// capability existed would otherwise make the kernel refuse the spawn
// outright, leaving no session at all rather than one without it.
caps.effective &= montauk::caps::self_delegable();
caps.delegable = caps.effective;
int pid;
+1
View File
@@ -30,6 +30,7 @@ static void maybe_run_setup_session() {
caps.effective = admin
? montauk::abi::CAP_ADMIN_SESSION
: montauk::abi::CAP_STANDARD_SESSION;
caps.effective &= montauk::caps::self_delegable();
// Matches launch_session(): a session delegates from what it holds, and
// the grant table decides which program receives which part of it.
caps.delegable = caps.effective;
+1
View File
@@ -153,6 +153,7 @@ ICONS=(
"apps/scalable/bluetooth.svg"
"panel/volume-level-high.svg"
"status/symbolic/audio-volume-high-symbolic.svg"
"status/symbolic/display-brightness-symbolic.svg"
"apps/scalable/gnome-logout.svg"
"apps/scalable/lock.svg"
# Consent prompt, one per capability (see montauk/consent.h). The clock,
+9 -3
View File
@@ -298,8 +298,14 @@ namespace montauk::abi {
never part of a session mask, never part of "all", and never
delegated by the compositor to what it launches. */
static constexpr uint64_t CAP_COMPOSITOR = 1ULL << 13;
static constexpr uint64_t CAP_ALL = (1ULL << 14) - 1;
static constexpr uint64_t CAP_STANDARD_SESSION = CAP_POWER_REQUEST | CAP_SUSPEND;
/* Set the backlight of the active display (SYS_DISPLAYBRIGHTNESS).
A seat preference rather than display administration, so every
session holds it; CAP_DISPLAY_ADMIN keeps mode setting. Reading the
level needs nothing. */
static constexpr uint64_t CAP_BRIGHTNESS = 1ULL << 14;
static constexpr uint64_t CAP_ALL = (1ULL << 15) - 1;
static constexpr uint64_t CAP_STANDARD_SESSION =
CAP_POWER_REQUEST | CAP_SUSPEND | CAP_BRIGHTNESS;
static constexpr uint64_t CAP_ADMIN_SESSION =
CAP_STANDARD_SESSION | CAP_PROCESS_ADMIN | CAP_STORAGE_ADMIN |
CAP_RAW_STORAGE | CAP_NETWORK_ADMIN | CAP_SET_TIME | CAP_USER_ADMIN |
@@ -351,7 +357,7 @@ namespace montauk::abi {
deferred delegation, not amplification. */
static constexpr uint64_t CAP_CONSENT_ELIGIBLE =
CAP_STORAGE_ADMIN | CAP_NETWORK_ADMIN | CAP_SET_TIME |
CAP_DISPLAY_ADMIN | CAP_LOG_READ;
CAP_DISPLAY_ADMIN | CAP_LOG_READ | CAP_BRIGHTNESS;
static_assert((CAP_CONSENT_ELIGIBLE & ~CAP_ADMIN_SESSION) == 0,
"consent can only hand out authority an admin session holds");
@@ -38,6 +38,7 @@ namespace caps {
{"log_read", montauk::abi::CAP_LOG_READ},
{"system_image", montauk::abi::CAP_SYSTEM_IMAGE},
{"compositor", montauk::abi::CAP_COMPOSITOR},
{"brightness", montauk::abi::CAP_BRIGHTNESS},
};
// Never implied by "all": each must be named explicitly in a grant.
+3 -1
View File
@@ -31,10 +31,12 @@ namespace consent {
"0:/os/icons/network-wired.svg"},
{montauk::abi::CAP_SET_TIME, "change the system clock and time zone",
"0:/os/icons/preferences-system-time.svg"},
{montauk::abi::CAP_DISPLAY_ADMIN, "change the screen resolution and brightness",
{montauk::abi::CAP_DISPLAY_ADMIN, "change the screen resolution",
"0:/os/icons/preferences-desktop-display.svg"},
{montauk::abi::CAP_LOG_READ, "read the system log, which can reveal what other apps and devices are doing",
"0:/os/icons/utilities-log-viewer.svg"},
{montauk::abi::CAP_BRIGHTNESS, "change the screen brightness",
"0:/os/icons/preferences-desktop-display.svg"},
};
// Fallback for a capability without its own icon.