feat: expand Intel GPU display management
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 20
|
#define MONTAUK_BUILD_NUMBER 24
|
||||||
|
|||||||
@@ -96,6 +96,22 @@ namespace montauk::abi {
|
|||||||
return GPU::Flip((int)index, (flags & 1) != 0);
|
return GPU::Flip((int)index, (flags & 1) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
|
||||||
|
return Drivers::Graphics::IntelGPU::GetDisplayInfo(out) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t Sys_DisplayModes(DisplayModeInfo* out, int maxCount) {
|
||||||
|
return Drivers::Graphics::IntelGPU::GetDisplayModes(out, maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t Sys_DisplaySetMode(int modeIndex) {
|
||||||
|
return Drivers::Graphics::IntelGPU::SetDisplayMode(modeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t Sys_DisplayBrightness(int percent) {
|
||||||
|
return Drivers::Graphics::IntelGPU::Brightness(percent);
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t Sys_TermSize() {
|
static uint64_t Sys_TermSize() {
|
||||||
// If the process is redirected to a GUI terminal, return those dimensions
|
// If the process is redirected to a GUI terminal, return those dimensions
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
|
|||||||
@@ -150,6 +150,20 @@ namespace montauk::abi {
|
|||||||
return (int64_t)Sys_FbMap();
|
return (int64_t)Sys_FbMap();
|
||||||
case SYS_FBFLIP:
|
case SYS_FBFLIP:
|
||||||
return Sys_FbFlip(frame->arg1, frame->arg2);
|
return Sys_FbFlip(frame->arg1, frame->arg2);
|
||||||
|
case SYS_DISPLAYINFO:
|
||||||
|
if (!UserMemory::Writable<DisplayInfo>(frame->arg1)) return -1;
|
||||||
|
return Sys_DisplayInfo((DisplayInfo*)frame->arg1);
|
||||||
|
case SYS_DISPLAYMODES:
|
||||||
|
// The Intel driver publishes at most 32 modes. Bounding this
|
||||||
|
// here also keeps the userspace range calculation overflow-free.
|
||||||
|
if ((int64_t)frame->arg2 < 0 || frame->arg2 > 32) return -1;
|
||||||
|
if (!UserMemory::Range(frame->arg1,
|
||||||
|
(uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return -1;
|
||||||
|
return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2);
|
||||||
|
case SYS_DISPLAYSETMODE:
|
||||||
|
return Sys_DisplaySetMode((int)frame->arg1);
|
||||||
|
case SYS_DISPLAYBRIGHTNESS:
|
||||||
|
return Sys_DisplayBrightness((int)frame->arg1);
|
||||||
case SYS_GETEXECPATH:
|
case SYS_GETEXECPATH:
|
||||||
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1;
|
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1;
|
||||||
return Sys_GetExecPath((char*)frame->arg1, frame->arg2);
|
return Sys_GetExecPath((char*)frame->arg1, frame->arg2);
|
||||||
|
|||||||
@@ -291,6 +291,12 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t SYS_STAT = 152;
|
static constexpr uint64_t SYS_STAT = 152;
|
||||||
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
||||||
|
|
||||||
|
/* Graphics.hpp -- display/modesetting control */
|
||||||
|
static constexpr uint64_t SYS_DISPLAYINFO = 154;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYMODES = 155;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
|
||||||
|
|
||||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||||
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
||||||
@@ -351,6 +357,51 @@ namespace montauk::abi {
|
|||||||
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DisplayConnectorType : uint8_t {
|
||||||
|
DISPLAY_CONNECTOR_UNKNOWN = 0,
|
||||||
|
DISPLAY_CONNECTOR_EDP = 1,
|
||||||
|
DISPLAY_CONNECTOR_DP = 2,
|
||||||
|
DISPLAY_CONNECTOR_HDMI = 3,
|
||||||
|
DISPLAY_CONNECTOR_DVI = 4,
|
||||||
|
DISPLAY_CONNECTOR_VGA = 5,
|
||||||
|
DISPLAY_CONNECTOR_LVDS = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_MODESET = 1u << 0;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_EDID = 1u << 1;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_BRIGHTNESS = 1u << 2;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_PAGE_FLIP = 1u << 3;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_VBLANK_IRQ = 1u << 4;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_SAFE_SWITCH = 1u << 5;
|
||||||
|
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_PREFERRED = 1u << 0;
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_CURRENT = 1u << 1;
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_DRIVER_VALID = 1u << 2;
|
||||||
|
|
||||||
|
struct DisplayModeInfo {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t refreshMilliHz;
|
||||||
|
uint32_t pixelClockKHz;
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t _pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DisplayInfo {
|
||||||
|
uint32_t capabilities;
|
||||||
|
uint32_t modeCount;
|
||||||
|
int32_t currentMode;
|
||||||
|
int32_t brightness; // 0..100, or -1 when unavailable
|
||||||
|
uint16_t deviceId;
|
||||||
|
uint8_t generation;
|
||||||
|
uint8_t connectorType;
|
||||||
|
uint8_t connected;
|
||||||
|
uint8_t _pad0[3];
|
||||||
|
char gpuName[64];
|
||||||
|
char connectorName[32];
|
||||||
|
char monitorName[64];
|
||||||
|
};
|
||||||
|
|
||||||
struct SysInfo {
|
struct SysInfo {
|
||||||
char osName[32];
|
char osName[32];
|
||||||
char osVersion[32];
|
char osVersion[32];
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace montauk::boot {
|
|||||||
// An adapter stamps the contract revision it produced into
|
// An adapter stamps the contract revision it produced into
|
||||||
// BootInfo::contractVersion; Boot.cpp refuses to continue on mismatch.
|
// BootInfo::contractVersion; Boot.cpp refuses to continue on mismatch.
|
||||||
// Bump this whenever the meaning or layout of BootInfo changes.
|
// Bump this whenever the meaning or layout of BootInfo changes.
|
||||||
static constexpr uint32_t ContractVersion = 1;
|
static constexpr uint32_t ContractVersion = 2;
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// Optional-capability advertisement.
|
// Optional-capability advertisement.
|
||||||
@@ -132,6 +132,12 @@ namespace montauk::boot {
|
|||||||
uint8_t redMaskSize, redMaskShift;
|
uint8_t redMaskSize, redMaskShift;
|
||||||
uint8_t greenMaskSize, greenMaskShift;
|
uint8_t greenMaskSize, greenMaskShift;
|
||||||
uint8_t blueMaskSize, blueMaskShift;
|
uint8_t blueMaskSize, blueMaskShift;
|
||||||
|
|
||||||
|
// Optional EDID blob supplied alongside the framebuffer. The pointer
|
||||||
|
// has bootloader-owned lifetime, so framebuffer consumers must copy
|
||||||
|
// it during early boot if they need it after reclaim.
|
||||||
|
const uint8_t* edid; // HHDM/direct-mapped virtual, or nullptr
|
||||||
|
uint64_t edidSize; // bytes; normally 128 or 256
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ namespace montauk::boot {
|
|||||||
out.framebuffer.greenMaskShift = fb->green_mask_shift;
|
out.framebuffer.greenMaskShift = fb->green_mask_shift;
|
||||||
out.framebuffer.blueMaskSize = fb->blue_mask_size;
|
out.framebuffer.blueMaskSize = fb->blue_mask_size;
|
||||||
out.framebuffer.blueMaskShift = fb->blue_mask_shift;
|
out.framebuffer.blueMaskShift = fb->blue_mask_shift;
|
||||||
|
out.framebuffer.edid = reinterpret_cast<const uint8_t*>(fb->edid);
|
||||||
|
out.framebuffer.edidSize = fb->edid_size;
|
||||||
out.features |= FeatureFramebuffer;
|
out.features |= FeatureFramebuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,29 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
static volatile uint64_t g_vblankCount = 0;
|
static volatile uint64_t g_vblankCount = 0;
|
||||||
static bool g_vblankIrqReady = false;
|
static bool g_vblankIrqReady = false;
|
||||||
|
|
||||||
|
// Modesetting/display-management state. The firmware-trained port and
|
||||||
|
// link are deliberately retained; the driver owns the active transcoder,
|
||||||
|
// timing catalogue, panel PWM, and validated timing transactions.
|
||||||
|
static constexpr int MaxDisplayModes = 32;
|
||||||
|
struct ModeRecord {
|
||||||
|
DisplayMode timing;
|
||||||
|
uint32_t refreshMilliHz;
|
||||||
|
uint32_t flags;
|
||||||
|
};
|
||||||
|
static ModeRecord g_modes[MaxDisplayModes] = {};
|
||||||
|
static int g_modeCount = 0;
|
||||||
|
static int g_currentMode = -1;
|
||||||
|
static uint32_t g_transTimingBase = TRANS_TIMING_A;
|
||||||
|
static uint32_t g_transConfReg = TRANS_CONF_A;
|
||||||
|
static uint32_t g_transDdiReg = TRANS_DDI_A;
|
||||||
|
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 char g_connectorName[32] = "Unknown";
|
||||||
|
static char g_monitorName[64] = "Unknown display";
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Register access helpers
|
// Register access helpers
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -73,6 +96,33 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
return *(volatile uint32_t*)(g_mmioBase + reg);
|
return *(volatile uint32_t*)(g_mmioBase + reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void CopyText(char* dst, uint64_t cap, const char* src) {
|
||||||
|
if (!dst || cap == 0) return;
|
||||||
|
uint64_t i = 0;
|
||||||
|
if (src) {
|
||||||
|
while (i + 1 < cap && src[i]) {
|
||||||
|
dst[i] = src[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CopyEdidText(char* dst, uint64_t cap, const uint8_t* src, int len) {
|
||||||
|
if (!dst || cap == 0) return;
|
||||||
|
int end = len;
|
||||||
|
while (end > 0 && (src[end - 1] == ' ' || src[end - 1] == '\n'
|
||||||
|
|| src[end - 1] == '\r' || src[end - 1] == '\0')) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
uint64_t out = 0;
|
||||||
|
for (int i = 0; i < end && out + 1 < cap; i++) {
|
||||||
|
uint8_t ch = src[i];
|
||||||
|
dst[out++] = (ch >= 0x20 && ch <= 0x7e) ? (char)ch : '?';
|
||||||
|
}
|
||||||
|
dst[out] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// PCI Detection
|
// PCI Detection
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -317,6 +367,350 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Connector discovery, EDID, and mode catalogue
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
static uint32_t ModeRefreshMilliHz(const DisplayMode& mode) {
|
||||||
|
uint64_t pixels = (uint64_t)mode.htotal * mode.vtotal;
|
||||||
|
if (pixels == 0 || mode.pixelClock == 0) return 0;
|
||||||
|
return (uint32_t)(((uint64_t)mode.pixelClock * 1000000ULL + pixels / 2) / pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SameMode(const ModeRecord& a, const DisplayMode& mode,
|
||||||
|
uint32_t refreshMilliHz) {
|
||||||
|
if (a.timing.hdisplay != mode.hdisplay || a.timing.vdisplay != mode.vdisplay)
|
||||||
|
return false;
|
||||||
|
if (a.timing.htotal && mode.htotal
|
||||||
|
&& (a.timing.htotal != mode.htotal || a.timing.vtotal != mode.vtotal))
|
||||||
|
return false;
|
||||||
|
uint32_t ar = a.refreshMilliHz;
|
||||||
|
uint32_t diff = ar > refreshMilliHz ? ar - refreshMilliHz : refreshMilliHz - ar;
|
||||||
|
return ar == 0 || refreshMilliHz == 0 || diff <= 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int AddMode(const DisplayMode& mode, uint32_t refreshMilliHz,
|
||||||
|
uint32_t flags) {
|
||||||
|
if (mode.hdisplay < 320 || mode.vdisplay < 200
|
||||||
|
|| mode.hdisplay > 16384 || mode.vdisplay > 16384)
|
||||||
|
return -1;
|
||||||
|
if (refreshMilliHz == 0) refreshMilliHz = ModeRefreshMilliHz(mode);
|
||||||
|
|
||||||
|
for (int i = 0; i < g_modeCount; i++) {
|
||||||
|
if (SameMode(g_modes[i], mode, refreshMilliHz)) {
|
||||||
|
g_modes[i].flags |= flags;
|
||||||
|
if (g_modes[i].timing.pixelClock == 0 && mode.pixelClock)
|
||||||
|
g_modes[i].timing = mode;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (g_modeCount >= MaxDisplayModes) return -1;
|
||||||
|
int index = g_modeCount++;
|
||||||
|
g_modes[index].timing = mode;
|
||||||
|
g_modes[index].refreshMilliHz = refreshMilliHz;
|
||||||
|
g_modes[index].flags = flags;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddSimpleMode(uint32_t width, uint32_t height, uint32_t refreshHz,
|
||||||
|
uint32_t flags = 0) {
|
||||||
|
DisplayMode mode = {};
|
||||||
|
mode.hdisplay = width;
|
||||||
|
mode.vdisplay = height;
|
||||||
|
AddMode(mode, refreshHz * 1000, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool EdidBlockValid(const uint8_t* block) {
|
||||||
|
uint8_t sum = 0;
|
||||||
|
for (int i = 0; i < 128; i++) sum = (uint8_t)(sum + block[i]);
|
||||||
|
return sum == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParseDetailedTiming(const uint8_t* dtd, DisplayMode& mode) {
|
||||||
|
uint32_t clock10KHz = (uint32_t)dtd[0] | ((uint32_t)dtd[1] << 8);
|
||||||
|
if (clock10KHz == 0) return false;
|
||||||
|
|
||||||
|
uint32_t hactive = dtd[2] | ((uint32_t)(dtd[4] & 0xF0) << 4);
|
||||||
|
uint32_t hblank = dtd[3] | ((uint32_t)(dtd[4] & 0x0F) << 8);
|
||||||
|
uint32_t vactive = dtd[5] | ((uint32_t)(dtd[7] & 0xF0) << 4);
|
||||||
|
uint32_t vblank = dtd[6] | ((uint32_t)(dtd[7] & 0x0F) << 8);
|
||||||
|
uint32_t hsyncOffset = dtd[8] | ((uint32_t)(dtd[11] & 0xC0) << 2);
|
||||||
|
uint32_t hsyncWidth = dtd[9] | ((uint32_t)(dtd[11] & 0x30) << 4);
|
||||||
|
uint32_t vsyncOffset = ((dtd[10] >> 4) & 0x0F)
|
||||||
|
| ((uint32_t)(dtd[11] & 0x0C) << 2);
|
||||||
|
uint32_t vsyncWidth = (dtd[10] & 0x0F)
|
||||||
|
| ((uint32_t)(dtd[11] & 0x03) << 4);
|
||||||
|
if (!hactive || !vactive || !hblank || !vblank) return false;
|
||||||
|
|
||||||
|
mode = {};
|
||||||
|
mode.hdisplay = hactive;
|
||||||
|
mode.hsyncStart = hactive + hsyncOffset;
|
||||||
|
mode.hsyncEnd = mode.hsyncStart + hsyncWidth;
|
||||||
|
mode.htotal = hactive + hblank;
|
||||||
|
mode.vdisplay = vactive;
|
||||||
|
mode.vsyncStart = vactive + vsyncOffset;
|
||||||
|
mode.vsyncEnd = mode.vsyncStart + vsyncWidth;
|
||||||
|
mode.vtotal = vactive + vblank;
|
||||||
|
mode.pixelClock = clock10KHz * 10;
|
||||||
|
mode.hsyncPositive = (dtd[17] & (1u << 1)) != 0;
|
||||||
|
mode.vsyncPositive = (dtd[17] & (1u << 2)) != 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseEdid() {
|
||||||
|
uint64_t edidSize = 0;
|
||||||
|
const uint8_t* edid = ::Graphics::Framebuffer::GetEdid(&edidSize);
|
||||||
|
if (!edid || edidSize < 128) return;
|
||||||
|
static constexpr uint8_t header[8] = {0x00, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0x00};
|
||||||
|
if (memcmp(edid, header, sizeof(header)) != 0 || !EdidBlockValid(edid)) {
|
||||||
|
KernelLogStream(WARNING, "IntelGPU") << "Boot EDID failed validation";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_edidValid = true;
|
||||||
|
|
||||||
|
// Established timings from EDID 1.3 section 3.8.
|
||||||
|
if (edid[35] & 0x20) AddSimpleMode(640, 480, 60);
|
||||||
|
if (edid[35] & 0x08) AddSimpleMode(640, 480, 72);
|
||||||
|
if (edid[35] & 0x04) AddSimpleMode(640, 480, 75);
|
||||||
|
if (edid[35] & 0x02) AddSimpleMode(800, 600, 56);
|
||||||
|
if (edid[35] & 0x01) AddSimpleMode(800, 600, 60);
|
||||||
|
if (edid[36] & 0x80) AddSimpleMode(800, 600, 72);
|
||||||
|
if (edid[36] & 0x40) AddSimpleMode(800, 600, 75);
|
||||||
|
if (edid[36] & 0x08) AddSimpleMode(1024, 768, 60);
|
||||||
|
if (edid[36] & 0x04) AddSimpleMode(1024, 768, 70);
|
||||||
|
if (edid[36] & 0x02) AddSimpleMode(1024, 768, 75);
|
||||||
|
if (edid[36] & 0x01) AddSimpleMode(1280, 1024, 75);
|
||||||
|
|
||||||
|
// Standard timings carry geometry and refresh but no porch/sync data.
|
||||||
|
for (int off = 38; off < 54; off += 2) {
|
||||||
|
if (edid[off] == 0x01 && edid[off + 1] == 0x01) continue;
|
||||||
|
uint32_t width = ((uint32_t)edid[off] + 31) * 8;
|
||||||
|
uint32_t aspect = (edid[off + 1] >> 6) & 3;
|
||||||
|
uint32_t height = aspect == 0 ? width * 10 / 16
|
||||||
|
: aspect == 1 ? width * 3 / 4
|
||||||
|
: aspect == 2 ? width * 4 / 5
|
||||||
|
: width * 9 / 16;
|
||||||
|
AddSimpleMode(width, height, (edid[off + 1] & 0x3F) + 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base-block detailed timings and monitor name descriptor.
|
||||||
|
bool firstDetailed = true;
|
||||||
|
for (int off = 54; off + 18 <= 126; off += 18) {
|
||||||
|
DisplayMode mode;
|
||||||
|
if (ParseDetailedTiming(edid + off, mode)) {
|
||||||
|
AddMode(mode, 0, firstDetailed ? montauk::abi::DISPLAY_MODE_PREFERRED : 0);
|
||||||
|
firstDetailed = false;
|
||||||
|
} else if (edid[off + 3] == 0xFC) {
|
||||||
|
CopyEdidText(g_monitorName, sizeof(g_monitorName), edid + off + 5, 13);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CTA-861 extension detailed timings. Other extension types are kept
|
||||||
|
// untouched; their mode encodings need format-specific parsers.
|
||||||
|
uint32_t blocks = 1 + edid[126];
|
||||||
|
if (blocks * 128 > edidSize) blocks = (uint32_t)(edidSize / 128);
|
||||||
|
for (uint32_t block = 1; block < blocks; block++) {
|
||||||
|
const uint8_t* ext = edid + block * 128;
|
||||||
|
if (!EdidBlockValid(ext) || ext[0] != 0x02) continue;
|
||||||
|
int start = ext[2];
|
||||||
|
if (start == 0 || start < 4 || start > 126) continue;
|
||||||
|
for (int off = start; off + 18 <= 127; off += 18) {
|
||||||
|
DisplayMode mode;
|
||||||
|
if (ParseDetailedTiming(ext + off, mode)) AddMode(mode, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "IntelGPU") << "EDID: " << g_monitorName
|
||||||
|
<< ", " << base::dec << (uint64_t)g_modeCount << " modes";
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TranscoderCandidate {
|
||||||
|
uint32_t timing;
|
||||||
|
uint32_t conf;
|
||||||
|
uint32_t ddi;
|
||||||
|
const char* name;
|
||||||
|
bool edp;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void DiscoverActiveTranscoder() {
|
||||||
|
static constexpr TranscoderCandidate candidates[] = {
|
||||||
|
{TRANS_TIMING_EDP, TRANS_CONF_EDP, TRANS_DDI_EDP, "eDP-1", true},
|
||||||
|
{TRANS_TIMING_A, TRANS_CONF_A, TRANS_DDI_A, "Pipe A", false},
|
||||||
|
{TRANS_TIMING_B, TRANS_CONF_B, TRANS_DDI_B, "Pipe B", false},
|
||||||
|
{TRANS_TIMING_C, TRANS_CONF_C, TRANS_DDI_C, "Pipe C", false},
|
||||||
|
};
|
||||||
|
|
||||||
|
int selected = -1;
|
||||||
|
for (int pass = 0; pass < 2 && selected < 0; pass++) {
|
||||||
|
for (int i = 0; i < (int)(sizeof(candidates) / sizeof(candidates[0])); i++) {
|
||||||
|
uint32_t conf = ReadReg(candidates[i].conf);
|
||||||
|
uint32_t ddi = ReadReg(candidates[i].ddi);
|
||||||
|
if (!(conf & PIPECONF_ENABLE)) continue;
|
||||||
|
if (pass == 0 && !(ddi & TRANS_DDI_ENABLE)) continue;
|
||||||
|
selected = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selected < 0) selected = 1; // conservative legacy Pipe A fallback
|
||||||
|
|
||||||
|
const auto& trans = candidates[selected];
|
||||||
|
g_transTimingBase = trans.timing;
|
||||||
|
g_transConfReg = trans.conf;
|
||||||
|
g_transDdiReg = trans.ddi;
|
||||||
|
g_connectorConnected = (ReadReg(trans.conf) & PIPECONF_ENABLE) != 0;
|
||||||
|
uint32_t ddi = ReadReg(trans.ddi);
|
||||||
|
|
||||||
|
if (g_gpuGen <= 7) {
|
||||||
|
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) {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_VGA;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "VGA-1");
|
||||||
|
} else if ((ReadReg(DP_B) | ReadReg(DP_C) | ReadReg(DP_D)) & (1u << 31)) {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "DP-1");
|
||||||
|
} else {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-1");
|
||||||
|
}
|
||||||
|
} else if (trans.edp) {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1");
|
||||||
|
} 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";
|
||||||
|
if (mode == 0) {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-");
|
||||||
|
} else if (mode == 1) {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DVI;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "DVI-");
|
||||||
|
} else {
|
||||||
|
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP;
|
||||||
|
CopyText(g_connectorName, sizeof(g_connectorName), "DP-");
|
||||||
|
}
|
||||||
|
uint64_t len = 0;
|
||||||
|
while (len + 1 < sizeof(g_connectorName) && g_connectorName[len]) len++;
|
||||||
|
CopyText(g_connectorName + len, sizeof(g_connectorName) - len, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
KernelLogStream(OK, "IntelGPU") << "Active transcoder " << trans.name
|
||||||
|
<< ", connector " << g_connectorName
|
||||||
|
<< (g_connectorConnected ? " connected" : " disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
static DisplayMode CaptureActiveMode() {
|
||||||
|
DisplayMode mode = {};
|
||||||
|
uint32_t htotal = ReadReg(g_transTimingBase + 0x00);
|
||||||
|
uint32_t hsync = ReadReg(g_transTimingBase + 0x08);
|
||||||
|
uint32_t vtotal = ReadReg(g_transTimingBase + 0x0C);
|
||||||
|
uint32_t vsync = ReadReg(g_transTimingBase + 0x14);
|
||||||
|
mode.hdisplay = (htotal & 0xFFFF) + 1;
|
||||||
|
mode.htotal = ((htotal >> 16) & 0xFFFF) + 1;
|
||||||
|
mode.hsyncStart = (hsync & 0xFFFF) + 1;
|
||||||
|
mode.hsyncEnd = ((hsync >> 16) & 0xFFFF) + 1;
|
||||||
|
mode.vdisplay = (vtotal & 0xFFFF) + 1;
|
||||||
|
mode.vtotal = ((vtotal >> 16) & 0xFFFF) + 1;
|
||||||
|
mode.vsyncStart = (vsync & 0xFFFF) + 1;
|
||||||
|
mode.vsyncEnd = ((vsync >> 16) & 0xFFFF) + 1;
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SelectCurrentMode() {
|
||||||
|
DisplayMode active = CaptureActiveMode();
|
||||||
|
if (active.hdisplay == 0 || active.hdisplay > 16384
|
||||||
|
|| active.vdisplay == 0 || active.vdisplay > 16384) {
|
||||||
|
active.hdisplay = (uint32_t)g_fbWidth;
|
||||||
|
active.vdisplay = (uint32_t)g_fbHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match the register totals against a detailed EDID timing to recover
|
||||||
|
// the firmware-selected pixel clock exactly.
|
||||||
|
for (int i = 0; i < g_modeCount; i++) {
|
||||||
|
const auto& timing = g_modes[i].timing;
|
||||||
|
if (timing.hdisplay == active.hdisplay && timing.vdisplay == active.vdisplay
|
||||||
|
&& timing.htotal == active.htotal && timing.vtotal == active.vtotal
|
||||||
|
&& timing.pixelClock != 0) {
|
||||||
|
active.pixelClock = timing.pixelClock;
|
||||||
|
active.hsyncPositive = timing.hsyncPositive;
|
||||||
|
active.vsyncPositive = timing.vsyncPositive;
|
||||||
|
g_currentMode = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t refresh = ModeRefreshMilliHz(active);
|
||||||
|
if (refresh == 0) refresh = 60000;
|
||||||
|
if (g_currentMode < 0) g_currentMode = AddMode(active, refresh, 0);
|
||||||
|
if (g_currentMode < 0 && g_modeCount == MaxDisplayModes) {
|
||||||
|
// A pathological EDID can fill the catalogue before the firmware
|
||||||
|
// mode is considered. The active mode is never optional: retain
|
||||||
|
// it by replacing the final non-preferred catalogue entry.
|
||||||
|
int replacement = MaxDisplayModes - 1;
|
||||||
|
for (int i = MaxDisplayModes - 1; i >= 0; i--) {
|
||||||
|
if (!(g_modes[i].flags & montauk::abi::DISPLAY_MODE_PREFERRED)) {
|
||||||
|
replacement = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_modes[replacement].timing = active;
|
||||||
|
g_modes[replacement].refreshMilliHz = refresh;
|
||||||
|
g_modes[replacement].flags = 0;
|
||||||
|
g_currentMode = replacement;
|
||||||
|
}
|
||||||
|
if (g_currentMode < 0) return;
|
||||||
|
|
||||||
|
g_modes[g_currentMode].flags |= montauk::abi::DISPLAY_MODE_CURRENT
|
||||||
|
| montauk::abi::DISPLAY_MODE_DRIVER_VALID;
|
||||||
|
uint32_t activeClock = g_modes[g_currentMode].timing.pixelClock;
|
||||||
|
for (int i = 0; i < g_modeCount; i++) {
|
||||||
|
const auto& mode = g_modes[i].timing;
|
||||||
|
if (mode.hdisplay == active.hdisplay && mode.vdisplay == active.vdisplay
|
||||||
|
&& mode.pixelClock != 0 && activeClock != 0
|
||||||
|
&& mode.pixelClock == activeClock) {
|
||||||
|
g_modes[i].flags |= montauk::abi::DISPLAY_MODE_DRIVER_VALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DiscoverPwm() {
|
||||||
|
g_pwmSupported = false;
|
||||||
|
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP
|
||||||
|
&& g_connectorType != montauk::abi::DISPLAY_CONNECTOR_LVDS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32_t pch = ReadReg(BLC_PWM_PCH_CTL2);
|
||||||
|
if ((pch >> 16) != 0) {
|
||||||
|
g_pwmCtlReg = BLC_PWM_PCH_CTL2;
|
||||||
|
g_pwmSupported = true;
|
||||||
|
} else {
|
||||||
|
uint32_t cpu = ReadReg(BLC_PWM_CPU_CTL);
|
||||||
|
if ((cpu >> 16) != 0) {
|
||||||
|
g_pwmCtlReg = BLC_PWM_CPU_CTL;
|
||||||
|
g_pwmSupported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KernelLogStream(INFO, "IntelGPU") << "Panel PWM "
|
||||||
|
<< (g_pwmSupported ? "available" : "not exposed");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializeDisplayManagement() {
|
||||||
|
g_modeCount = 0;
|
||||||
|
g_currentMode = -1;
|
||||||
|
g_edidValid = false;
|
||||||
|
CopyText(g_monitorName, sizeof(g_monitorName), "Unknown display");
|
||||||
|
DiscoverActiveTranscoder();
|
||||||
|
ParseEdid();
|
||||||
|
SelectCurrentMode();
|
||||||
|
DiscoverPwm();
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// GTT Initialization
|
// GTT Initialization
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -850,6 +1244,7 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
|
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
InitializeDisplayManagement();
|
||||||
|
|
||||||
if (!InitializeGtt()) {
|
if (!InitializeGtt()) {
|
||||||
KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT";
|
KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT";
|
||||||
@@ -910,6 +1305,7 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
|
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
InitializeDisplayManagement();
|
||||||
|
|
||||||
// Step 5: Initialize GTT
|
// Step 5: Initialize GTT
|
||||||
if (!InitializeGtt()) {
|
if (!InitializeGtt()) {
|
||||||
@@ -976,6 +1372,205 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
return g_fbPitch;
|
return g_fbPitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Display management public API
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
static bool WaitTranscoderState(bool enabled) {
|
||||||
|
for (int i = 0; i < 200000; i++) {
|
||||||
|
bool state = (ReadReg(g_transConfReg) & PIPECONF_STATE) != 0;
|
||||||
|
if (state == enabled) return true;
|
||||||
|
asm volatile("pause");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimingSnapshot {
|
||||||
|
uint32_t htotal, hblank, hsync;
|
||||||
|
uint32_t vtotal, vblank, vsync;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TimingSnapshot ReadTimingSnapshot() {
|
||||||
|
return {
|
||||||
|
ReadReg(g_transTimingBase + 0x00),
|
||||||
|
ReadReg(g_transTimingBase + 0x04),
|
||||||
|
ReadReg(g_transTimingBase + 0x08),
|
||||||
|
ReadReg(g_transTimingBase + 0x0C),
|
||||||
|
ReadReg(g_transTimingBase + 0x10),
|
||||||
|
ReadReg(g_transTimingBase + 0x14),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteTimingSnapshot(const TimingSnapshot& timing) {
|
||||||
|
WriteReg(g_transTimingBase + 0x00, timing.htotal);
|
||||||
|
WriteReg(g_transTimingBase + 0x04, timing.hblank);
|
||||||
|
WriteReg(g_transTimingBase + 0x08, timing.hsync);
|
||||||
|
WriteReg(g_transTimingBase + 0x0C, timing.vtotal);
|
||||||
|
WriteReg(g_transTimingBase + 0x10, timing.vblank);
|
||||||
|
WriteReg(g_transTimingBase + 0x14, timing.vsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t PackTiming(uint32_t start, uint32_t end) {
|
||||||
|
return ((end - 1) << 16) | (start - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ApplyModeTiming(const DisplayMode& mode) {
|
||||||
|
if (!g_connectorConnected || mode.hdisplay == 0 || mode.vdisplay == 0
|
||||||
|
|| mode.htotal <= mode.hdisplay || mode.vtotal <= mode.vdisplay
|
||||||
|
|| mode.hsyncStart < mode.hdisplay || mode.hsyncEnd <= mode.hsyncStart
|
||||||
|
|| mode.hsyncEnd > mode.htotal || mode.vsyncStart < mode.vdisplay
|
||||||
|
|| mode.vsyncEnd <= mode.vsyncStart || mode.vsyncEnd > mode.vtotal)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TimingSnapshot oldTiming = ReadTimingSnapshot();
|
||||||
|
uint32_t oldConf = ReadReg(g_transConfReg);
|
||||||
|
uint32_t oldPlane = ReadReg(DSPACNTR);
|
||||||
|
uint64_t oldSurface = (g_flipSupported && g_frontBuffer == 1)
|
||||||
|
? g_buf1GttOffset
|
||||||
|
: (g_flipSupported ? g_fbGttOffsetA : g_fbGttOffset);
|
||||||
|
|
||||||
|
// A timing-only modeset retains the firmware-trained DDI, PLL and link.
|
||||||
|
// Disable fetch and the transcoder, program all timing registers as one
|
||||||
|
// transaction, then restore them in the inverse order.
|
||||||
|
if (oldPlane & DISP_ENABLE) {
|
||||||
|
WriteReg(DSPACNTR, oldPlane & ~DISP_ENABLE);
|
||||||
|
WriteReg(DSPASURF, (uint32_t)oldSurface);
|
||||||
|
(void)ReadReg(DSPASURF);
|
||||||
|
}
|
||||||
|
WriteReg(g_transConfReg, oldConf & ~PIPECONF_ENABLE);
|
||||||
|
if (!WaitTranscoderState(false)) {
|
||||||
|
WriteReg(DSPACNTR, oldPlane);
|
||||||
|
WriteReg(DSPASURF, (uint32_t)oldSurface);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimingSnapshot next = {
|
||||||
|
PackTiming(mode.hdisplay, mode.htotal),
|
||||||
|
PackTiming(mode.hdisplay, mode.htotal),
|
||||||
|
PackTiming(mode.hsyncStart, mode.hsyncEnd),
|
||||||
|
PackTiming(mode.vdisplay, mode.vtotal),
|
||||||
|
PackTiming(mode.vdisplay, mode.vtotal),
|
||||||
|
PackTiming(mode.vsyncStart, mode.vsyncEnd),
|
||||||
|
};
|
||||||
|
WriteTimingSnapshot(next);
|
||||||
|
WriteReg(g_transConfReg, oldConf | PIPECONF_ENABLE);
|
||||||
|
if (!WaitTranscoderState(true)) {
|
||||||
|
// Roll back while the transcoder is stopped. A failed timing must
|
||||||
|
// never leave the panel black merely because userspace requested it.
|
||||||
|
WriteReg(g_transConfReg, oldConf & ~PIPECONF_ENABLE);
|
||||||
|
(void)WaitTranscoderState(false);
|
||||||
|
WriteTimingSnapshot(oldTiming);
|
||||||
|
WriteReg(g_transConfReg, oldConf);
|
||||||
|
(void)WaitTranscoderState((oldConf & PIPECONF_ENABLE) != 0);
|
||||||
|
WriteReg(DSPACNTR, oldPlane);
|
||||||
|
WriteReg(DSPASURF, (uint32_t)oldSurface);
|
||||||
|
(void)ReadReg(DSPASURF);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteReg(DSPACNTR, oldPlane);
|
||||||
|
WriteReg(DSPASURF, (uint32_t)oldSurface);
|
||||||
|
(void)ReadReg(DSPASURF);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetDisplayInfo(montauk::abi::DisplayInfo* out) {
|
||||||
|
if (!out) return false;
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
out->brightness = -1;
|
||||||
|
out->currentMode = -1;
|
||||||
|
if (!g_initialized) return false;
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (g_vblankIrqReady) out->capabilities |= montauk::abi::DISPLAY_CAP_VBLANK_IRQ;
|
||||||
|
for (int i = 0; i < g_modeCount; i++) {
|
||||||
|
if (i != g_currentMode
|
||||||
|
&& (g_modes[i].flags & montauk::abi::DISPLAY_MODE_DRIVER_VALID)) {
|
||||||
|
out->capabilities |= montauk::abi::DISPLAY_CAP_SAFE_SWITCH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out->modeCount = (uint32_t)g_modeCount;
|
||||||
|
out->currentMode = g_currentMode;
|
||||||
|
out->brightness = Brightness(-1);
|
||||||
|
out->deviceId = g_gpuInfo.deviceId;
|
||||||
|
out->generation = g_gpuInfo.gen;
|
||||||
|
out->connectorType = g_connectorType;
|
||||||
|
out->connected = g_connectorConnected ? 1 : 0;
|
||||||
|
CopyText(out->gpuName, sizeof(out->gpuName),
|
||||||
|
g_gpuInfo.name ? g_gpuInfo.name : "Intel GPU");
|
||||||
|
CopyText(out->connectorName, sizeof(out->connectorName), g_connectorName);
|
||||||
|
CopyText(out->monitorName, sizeof(out->monitorName), g_monitorName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetDisplayModes(montauk::abi::DisplayModeInfo* out, int maxCount) {
|
||||||
|
if (!g_initialized || maxCount < 0) return -1;
|
||||||
|
int count = g_modeCount < maxCount ? g_modeCount : maxCount;
|
||||||
|
if (count > 0 && !out) return -1;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
out[i].width = g_modes[i].timing.hdisplay;
|
||||||
|
out[i].height = g_modes[i].timing.vdisplay;
|
||||||
|
out[i].refreshMilliHz = g_modes[i].refreshMilliHz;
|
||||||
|
out[i].pixelClockKHz = g_modes[i].timing.pixelClock;
|
||||||
|
out[i].flags = g_modes[i].flags;
|
||||||
|
out[i]._pad = 0;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SetDisplayMode(int modeIndex) {
|
||||||
|
if (!g_initialized || modeIndex < 0 || modeIndex >= g_modeCount) return -1;
|
||||||
|
if (g_currentMode < 0 || g_currentMode >= g_modeCount) return -1;
|
||||||
|
if (!(g_modes[modeIndex].flags & montauk::abi::DISPLAY_MODE_DRIVER_VALID))
|
||||||
|
return -2;
|
||||||
|
if (modeIndex == g_currentMode) return modeIndex;
|
||||||
|
|
||||||
|
// Link rate and framebuffer geometry remain fixed. Only timings with
|
||||||
|
// the same active area and pixel clock are safe without retraining the
|
||||||
|
// firmware-established HDMI/DP/eDP link.
|
||||||
|
const DisplayMode& current = g_modes[g_currentMode].timing;
|
||||||
|
const DisplayMode& requested = g_modes[modeIndex].timing;
|
||||||
|
if (requested.hdisplay != current.hdisplay
|
||||||
|
|| requested.vdisplay != current.vdisplay
|
||||||
|
|| requested.pixelClock == 0
|
||||||
|
|| requested.pixelClock != current.pixelClock)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
if (!ApplyModeTiming(requested)) {
|
||||||
|
KernelLogStream(ERROR, "IntelGPU") << "Modeset transaction failed; previous mode restored";
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_modes[g_currentMode].flags &= ~montauk::abi::DISPLAY_MODE_CURRENT;
|
||||||
|
g_currentMode = modeIndex;
|
||||||
|
g_modes[g_currentMode].flags |= montauk::abi::DISPLAY_MODE_CURRENT;
|
||||||
|
KernelLogStream(OK, "IntelGPU") << "Mode set to " << base::dec
|
||||||
|
<< (uint64_t)requested.hdisplay << "x" << (uint64_t)requested.vdisplay
|
||||||
|
<< " @ " << (uint64_t)(g_modes[modeIndex].refreshMilliHz / 1000) << "Hz";
|
||||||
|
return modeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (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);
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Page flip public API
|
// Page flip public API
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <Pci/Pci.hpp>
|
#include <Pci/Pci.hpp>
|
||||||
|
#include <Api/Syscall.hpp>
|
||||||
|
|
||||||
namespace Drivers::Graphics::IntelGPU {
|
namespace Drivers::Graphics::IntelGPU {
|
||||||
|
|
||||||
@@ -265,6 +266,32 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
static constexpr uint32_t HDMI_B = 0x61140; // HDMI-B (same as DVOB on some gens)
|
static constexpr uint32_t HDMI_B = 0x61140; // HDMI-B (same as DVOB on some gens)
|
||||||
static constexpr uint32_t HDMI_C = 0x61160; // HDMI-C (same as DVOC on some gens)
|
static constexpr uint32_t HDMI_C = 0x61160; // HDMI-C (same as DVOC on some gens)
|
||||||
|
|
||||||
|
// Modern transcoder instances. Pipe source/plane registers remain
|
||||||
|
// pipe-relative, while link timings live in the selected transcoder.
|
||||||
|
static constexpr uint32_t TRANS_TIMING_A = 0x60000;
|
||||||
|
static constexpr uint32_t TRANS_TIMING_B = 0x61000;
|
||||||
|
static constexpr uint32_t TRANS_TIMING_C = 0x62000;
|
||||||
|
static constexpr uint32_t TRANS_TIMING_EDP = 0x6F000;
|
||||||
|
static constexpr uint32_t TRANS_CONF_A = 0x70008;
|
||||||
|
static constexpr uint32_t TRANS_CONF_B = 0x71008;
|
||||||
|
static constexpr uint32_t TRANS_CONF_C = 0x72008;
|
||||||
|
static constexpr uint32_t TRANS_CONF_EDP = 0x7F008;
|
||||||
|
static constexpr uint32_t TRANS_DDI_A = 0x60400;
|
||||||
|
static constexpr uint32_t TRANS_DDI_B = 0x61400;
|
||||||
|
static constexpr uint32_t TRANS_DDI_C = 0x62400;
|
||||||
|
static constexpr uint32_t TRANS_DDI_EDP = 0x6F400;
|
||||||
|
static constexpr uint32_t TRANS_DDI_ENABLE = (1u << 31);
|
||||||
|
static constexpr uint32_t TRANS_DDI_PORT_MASK = (0xFu << 27);
|
||||||
|
static constexpr uint32_t TRANS_DDI_MODE_MASK = (0x7u << 24);
|
||||||
|
|
||||||
|
// PCH/CPU panel PWM. Firmware owns the frequency; the driver only changes
|
||||||
|
// the duty field and preserves enable/polarity/reserved bits.
|
||||||
|
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);
|
||||||
|
|
||||||
// ADPA bits
|
// ADPA bits
|
||||||
static constexpr uint32_t ADPA_DAC_ENABLE = (1u << 31);
|
static constexpr uint32_t ADPA_DAC_ENABLE = (1u << 31);
|
||||||
static constexpr uint32_t ADPA_PIPE_B_SELECT = (1u << 30);
|
static constexpr uint32_t ADPA_PIPE_B_SELECT = (1u << 30);
|
||||||
@@ -462,6 +489,16 @@ namespace Drivers::Graphics::IntelGPU {
|
|||||||
uint64_t GetHeight();
|
uint64_t GetHeight();
|
||||||
uint64_t GetPitch();
|
uint64_t GetPitch();
|
||||||
|
|
||||||
|
// Display connector, EDID mode catalogue, and modesetting controls.
|
||||||
|
bool GetDisplayInfo(montauk::abi::DisplayInfo* out);
|
||||||
|
int GetDisplayModes(montauk::abi::DisplayModeInfo* out, int maxCount);
|
||||||
|
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.
|
||||||
|
int Brightness(int percent);
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Page flipping (double-buffered scanout)
|
// Page flipping (double-buffered scanout)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -17,12 +17,20 @@ namespace Graphics::Framebuffer {
|
|||||||
static uint64_t g_FbWidth = 0;
|
static uint64_t g_FbWidth = 0;
|
||||||
static uint64_t g_FbHeight = 0;
|
static uint64_t g_FbHeight = 0;
|
||||||
static uint64_t g_FbPitch = 0; // in bytes
|
static uint64_t g_FbPitch = 0; // in bytes
|
||||||
|
static uint8_t g_Edid[256] = {};
|
||||||
|
static uint64_t g_EdidSize = 0;
|
||||||
|
|
||||||
void Initialize(const montauk::boot::Framebuffer& framebuffer) {
|
void Initialize(const montauk::boot::Framebuffer& framebuffer) {
|
||||||
g_FbBase = reinterpret_cast<uint32_t*>(framebuffer.address);
|
g_FbBase = reinterpret_cast<uint32_t*>(framebuffer.address);
|
||||||
g_FbWidth = framebuffer.width;
|
g_FbWidth = framebuffer.width;
|
||||||
g_FbHeight = framebuffer.height;
|
g_FbHeight = framebuffer.height;
|
||||||
g_FbPitch = framebuffer.pitch;
|
g_FbPitch = framebuffer.pitch;
|
||||||
|
g_EdidSize = framebuffer.edidSize;
|
||||||
|
if (g_EdidSize > sizeof(g_Edid)) g_EdidSize = sizeof(g_Edid);
|
||||||
|
if (framebuffer.edid == nullptr) g_EdidSize = 0;
|
||||||
|
for (uint64_t i = 0; i < g_EdidSize; i++) {
|
||||||
|
g_Edid[i] = framebuffer.edid[i];
|
||||||
|
}
|
||||||
|
|
||||||
Kt::KernelLogStream(Kt::OK, "Graphics") << "Framebuffer initialized ("
|
Kt::KernelLogStream(Kt::OK, "Graphics") << "Framebuffer initialized ("
|
||||||
<< (uint64_t)g_FbWidth << "x" << (uint64_t)g_FbHeight << ")";
|
<< (uint64_t)g_FbWidth << "x" << (uint64_t)g_FbHeight << ")";
|
||||||
@@ -32,6 +40,10 @@ namespace Graphics::Framebuffer {
|
|||||||
uint64_t GetWidth() { return g_FbWidth; }
|
uint64_t GetWidth() { return g_FbWidth; }
|
||||||
uint64_t GetHeight() { return g_FbHeight; }
|
uint64_t GetHeight() { return g_FbHeight; }
|
||||||
uint64_t GetPitch() { return g_FbPitch; }
|
uint64_t GetPitch() { return g_FbPitch; }
|
||||||
|
const uint8_t* GetEdid(uint64_t* size) {
|
||||||
|
if (size) *size = g_EdidSize;
|
||||||
|
return g_EdidSize ? g_Edid : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void Set(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch) {
|
void Set(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch) {
|
||||||
g_FbBase = base;
|
g_FbBase = base;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace Graphics::Framebuffer {
|
|||||||
uint64_t GetWidth();
|
uint64_t GetWidth();
|
||||||
uint64_t GetHeight();
|
uint64_t GetHeight();
|
||||||
uint64_t GetPitch();
|
uint64_t GetPitch();
|
||||||
|
const uint8_t* GetEdid(uint64_t* size);
|
||||||
|
|
||||||
void Set(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch);
|
void Set(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch);
|
||||||
uint64_t GetPhysBase();
|
uint64_t GetPhysBase();
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ BINDIR := bin
|
|||||||
PROGRAMS := $(notdir $(wildcard src/*))
|
PROGRAMS := $(notdir $(wildcard src/*))
|
||||||
|
|
||||||
# Programs with custom Makefiles (built separately).
|
# Programs with custom Makefiles (built separately).
|
||||||
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
|
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
|
||||||
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
||||||
|
|
||||||
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
|
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
|
||||||
@@ -89,9 +89,9 @@ WPDIR := data/wallpapers
|
|||||||
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
WPSRC := $(wildcard $(WPDIR)/*.jpg)
|
||||||
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
|
||||||
|
|
||||||
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
|
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
|
||||||
|
|
||||||
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
|
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
|
||||||
|
|
||||||
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
# Build BearSSL static library (cross-compiled for freestanding x86_64).
|
||||||
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
|
||||||
@@ -192,6 +192,10 @@ bluetooth: libc
|
|||||||
network: libc
|
network: libc
|
||||||
$(MAKE) -C src/network
|
$(MAKE) -C src/network
|
||||||
|
|
||||||
|
# Build display/graphics settings applet (depends on libc).
|
||||||
|
display: libc
|
||||||
|
$(MAKE) -C src/display
|
||||||
|
|
||||||
# Build terminal standalone GUI tool (depends on libc).
|
# Build terminal standalone GUI tool (depends on libc).
|
||||||
terminal: libc
|
terminal: libc
|
||||||
$(MAKE) -C src/terminal
|
$(MAKE) -C src/terminal
|
||||||
@@ -316,7 +320,7 @@ crashpad: libc
|
|||||||
$(MAKE) -C src/crashpad
|
$(MAKE) -C src/crashpad
|
||||||
|
|
||||||
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
|
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
|
||||||
install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
|
install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
|
||||||
../scripts/install_apps.sh
|
../scripts/install_apps.sh
|
||||||
|
|
||||||
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
|
||||||
|
|||||||
@@ -213,6 +213,12 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t SYS_STAT = 152;
|
static constexpr uint64_t SYS_STAT = 152;
|
||||||
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
||||||
|
|
||||||
|
// Display/modesetting control
|
||||||
|
static constexpr uint64_t SYS_DISPLAYINFO = 154;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYMODES = 155;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
|
||||||
|
|
||||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||||
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
||||||
@@ -312,6 +318,51 @@ namespace montauk::abi {
|
|||||||
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DisplayConnectorType : uint8_t {
|
||||||
|
DISPLAY_CONNECTOR_UNKNOWN = 0,
|
||||||
|
DISPLAY_CONNECTOR_EDP = 1,
|
||||||
|
DISPLAY_CONNECTOR_DP = 2,
|
||||||
|
DISPLAY_CONNECTOR_HDMI = 3,
|
||||||
|
DISPLAY_CONNECTOR_DVI = 4,
|
||||||
|
DISPLAY_CONNECTOR_VGA = 5,
|
||||||
|
DISPLAY_CONNECTOR_LVDS = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_MODESET = 1u << 0;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_EDID = 1u << 1;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_BRIGHTNESS = 1u << 2;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_PAGE_FLIP = 1u << 3;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_VBLANK_IRQ = 1u << 4;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_SAFE_SWITCH = 1u << 5;
|
||||||
|
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_PREFERRED = 1u << 0;
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_CURRENT = 1u << 1;
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_DRIVER_VALID = 1u << 2;
|
||||||
|
|
||||||
|
struct DisplayModeInfo {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t refreshMilliHz;
|
||||||
|
uint32_t pixelClockKHz;
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t _pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DisplayInfo {
|
||||||
|
uint32_t capabilities;
|
||||||
|
uint32_t modeCount;
|
||||||
|
int32_t currentMode;
|
||||||
|
int32_t brightness; // 0..100, or -1 when unavailable
|
||||||
|
uint16_t deviceId;
|
||||||
|
uint8_t generation;
|
||||||
|
uint8_t connectorType;
|
||||||
|
uint8_t connected;
|
||||||
|
uint8_t _pad0[3];
|
||||||
|
char gpuName[64];
|
||||||
|
char connectorName[32];
|
||||||
|
char monitorName[64];
|
||||||
|
};
|
||||||
|
|
||||||
struct SysInfo {
|
struct SysInfo {
|
||||||
char osName[32];
|
char osName[32];
|
||||||
char osVersion[32];
|
char osVersion[32];
|
||||||
|
|||||||
@@ -161,24 +161,58 @@ struct Canvas {
|
|||||||
|
|
||||||
// ---- Text ----
|
// ---- Text ----
|
||||||
|
|
||||||
|
void text_bitmap(int x, int y, const char* str, Color c, int scale = 1) {
|
||||||
|
if (!str || !font_data || scale <= 0) return;
|
||||||
|
uint32_t pixel = c.to_pixel();
|
||||||
|
int cx = x;
|
||||||
|
for (int i = 0; str[i]; i++, cx += FONT_WIDTH * scale) {
|
||||||
|
const uint8_t* glyph =
|
||||||
|
&font_data[(unsigned char)str[i] * FONT_HEIGHT];
|
||||||
|
for (int row = 0; row < FONT_HEIGHT; row++) {
|
||||||
|
uint8_t bits = glyph[row];
|
||||||
|
if (!bits) continue;
|
||||||
|
for (int col = 0; col < FONT_WIDTH; col++) {
|
||||||
|
if (!(bits & (0x80 >> col))) continue;
|
||||||
|
int px = cx + col * scale;
|
||||||
|
int py = y + row * scale;
|
||||||
|
for (int sy = 0; sy < scale; sy++) {
|
||||||
|
int dy = py + sy;
|
||||||
|
if (dy < 0 || dy >= h) continue;
|
||||||
|
for (int sx = 0; sx < scale; sx++) {
|
||||||
|
int dx = px + sx;
|
||||||
|
if (dx >= 0 && dx < w) pixels[dy * w + dx] = pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void text(int x, int y, const char* str, Color c) {
|
void text(int x, int y, const char* str, Color c) {
|
||||||
if (fonts::system_font && fonts::system_font->valid) {
|
if (fonts::system_font && fonts::system_font->valid) {
|
||||||
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::UI_SIZE);
|
if (fonts::system_font->draw_to_buffer(
|
||||||
|
pixels, w, h, x, y, str, c, fonts::UI_SIZE))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
text_bitmap(x, y, str, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
void text_2x(int x, int y, const char* str, Color c) {
|
void text_2x(int x, int y, const char* str, Color c) {
|
||||||
if (fonts::system_font && fonts::system_font->valid) {
|
if (fonts::system_font && fonts::system_font->valid) {
|
||||||
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::LARGE_SIZE);
|
if (fonts::system_font->draw_to_buffer(
|
||||||
|
pixels, w, h, x, y, str, c, fonts::LARGE_SIZE))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
text_bitmap(x, y, str, c, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void text_mono(int x, int y, const char* str, Color c) {
|
void text_mono(int x, int y, const char* str, Color c) {
|
||||||
if (fonts::mono && fonts::mono->valid) {
|
if (fonts::mono && fonts::mono->valid) {
|
||||||
fonts::mono->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::TERM_SIZE);
|
if (fonts::mono->draw_to_buffer(
|
||||||
return;
|
pixels, w, h, x, y, str, c, fonts::TERM_SIZE))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
text(x, y, str, c);
|
text_bitmap(x, y, str, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Icons ----
|
// ---- Icons ----
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ namespace gui {
|
|||||||
static constexpr int FONT_WIDTH = 8;
|
static constexpr int FONT_WIDTH = 8;
|
||||||
static constexpr int FONT_HEIGHT = 16;
|
static constexpr int FONT_HEIGHT = 16;
|
||||||
|
|
||||||
// Defined in font_data.cpp
|
// Defined by programs that ship the built-in VGA fallback. Keep the symbol
|
||||||
extern const uint8_t font_data[256 * 16];
|
// weak so Canvas remains usable by small apps that intentionally rely only on
|
||||||
|
// TrueType fonts, while login/desktop can recover when TrueType setup fails.
|
||||||
|
extern const uint8_t font_data[256 * 16] __attribute__((weak));
|
||||||
|
|
||||||
// Dynamic font height: TTF line height or 16 (bitmap fallback)
|
// Dynamic font height: TTF line height or 16 (bitmap fallback)
|
||||||
inline int system_font_height() {
|
inline int system_font_height() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <montauk/syscall.h>
|
#include <montauk/syscall.h>
|
||||||
#include <montauk/string.h>
|
#include <montauk/string.h>
|
||||||
|
#include <montauk/config.h>
|
||||||
#include "gui/gui.hpp"
|
#include "gui/gui.hpp"
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
@@ -61,6 +62,20 @@ class Framebuffer {
|
|||||||
return 0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
return 0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool prefer_hardware_page_flip() {
|
||||||
|
char user[64] = {};
|
||||||
|
if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) {
|
||||||
|
auto doc = montauk::config::load_user(user, "display");
|
||||||
|
bool enabled = doc.get_bool("graphics.tear_free", true);
|
||||||
|
doc.destroy();
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
auto doc = montauk::config::load("display");
|
||||||
|
bool enabled = doc.get_bool("graphics.tear_free", true);
|
||||||
|
doc.destroy();
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Framebuffer() : hw_fb(nullptr), hw_fb2(nullptr), back_buf(nullptr),
|
Framebuffer() : hw_fb(nullptr), hw_fb2(nullptr), back_buf(nullptr),
|
||||||
fb_width(0), fb_height(0), fb_pitch(0), hw_next(1) {
|
fb_width(0), fb_height(0), fb_pitch(0), hw_next(1) {
|
||||||
@@ -76,7 +91,8 @@ public:
|
|||||||
|
|
||||||
// Hardware page flipping: fb_map() maps the second scanout buffer
|
// Hardware page flipping: fb_map() maps the second scanout buffer
|
||||||
// directly after the first when the kernel supports flipping.
|
// directly after the first when the kernel supports flipping.
|
||||||
if (hw_fb && montauk::fb_flip(-1, 0) == 1) {
|
if (hw_fb && prefer_hardware_page_flip()
|
||||||
|
&& montauk::fb_flip(-1, 0) == 1) {
|
||||||
uint64_t pages = ((uint64_t)fb_height * fb_pitch + 0xFFF) / 0x1000;
|
uint64_t pages = ((uint64_t)fb_height * fb_pitch + 0xFFF) / 0x1000;
|
||||||
hw_fb2 = (uint32_t*)((uint8_t*)hw_fb + pages * 0x1000);
|
hw_fb2 = (uint32_t*)((uint8_t*)hw_fb + pages * 0x1000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,13 +311,14 @@ struct TrueTypeFont {
|
|||||||
draw(fb, x, y, text, fg, pixel_size);
|
draw(fb, x, y, text, fg, pixel_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
|
bool draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
|
||||||
int x, int y, const char* text,
|
int x, int y, const char* text,
|
||||||
Color color, int pixel_size) {
|
Color color, int pixel_size) {
|
||||||
if (!valid) return;
|
if (!valid) return false;
|
||||||
GlyphCache* gc = get_cache(pixel_size);
|
GlyphCache* gc = get_cache(pixel_size);
|
||||||
int cx = x;
|
int cx = x;
|
||||||
int baseline = y + gc->ascent;
|
int baseline = y + gc->ascent;
|
||||||
|
bool drew_pixel = false;
|
||||||
|
|
||||||
for (int i = 0; text[i]; i++) {
|
for (int i = 0; text[i]; i++) {
|
||||||
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
|
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
|
||||||
@@ -334,6 +335,7 @@ struct TrueTypeFont {
|
|||||||
if (dx < 0 || dx >= buf_w) continue;
|
if (dx < 0 || dx >= buf_w) continue;
|
||||||
uint8_t alpha = g->bitmap[row * g->width + col];
|
uint8_t alpha = g->bitmap[row * g->width + col];
|
||||||
if (alpha == 0) continue;
|
if (alpha == 0) continue;
|
||||||
|
drew_pixel = true;
|
||||||
|
|
||||||
if (alpha == 255) {
|
if (alpha == 255) {
|
||||||
pixels[dy * buf_w + dx] =
|
pixels[dy * buf_w + dx] =
|
||||||
@@ -356,6 +358,7 @@ struct TrueTypeFont {
|
|||||||
}
|
}
|
||||||
cx += g->advance;
|
cx += g->advance;
|
||||||
}
|
}
|
||||||
|
return drew_pixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw text to buffer with clip rectangle (pixels outside clip_x..clip_x+clip_w are not drawn)
|
// Draw text to buffer with clip rectangle (pixels outside clip_x..clip_x+clip_w are not drawn)
|
||||||
|
|||||||
@@ -177,6 +177,10 @@ extern "C" {
|
|||||||
#define MTK_SYS_GETEXECPATH 151
|
#define MTK_SYS_GETEXECPATH 151
|
||||||
#define MTK_SYS_STAT 152
|
#define MTK_SYS_STAT 152
|
||||||
#define MTK_SYS_SETUNIXTIME 153
|
#define MTK_SYS_SETUNIXTIME 153
|
||||||
|
#define MTK_SYS_DISPLAYINFO 154
|
||||||
|
#define MTK_SYS_DISPLAYMODES 155
|
||||||
|
#define MTK_SYS_DISPLAYSETMODE 156
|
||||||
|
#define MTK_SYS_DISPLAYBRIGHTNESS 157
|
||||||
/* @SYSCALLS-END */
|
/* @SYSCALLS-END */
|
||||||
|
|
||||||
#define MTK_SOCK_TCP 1
|
#define MTK_SOCK_TCP 1
|
||||||
|
|||||||
@@ -343,6 +343,22 @@ namespace montauk {
|
|||||||
return syscall2(montauk::abi::SYS_FBFLIP, (uint64_t)index, flags);
|
return syscall2(montauk::abi::SYS_FBFLIP, (uint64_t)index, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int display_info(montauk::abi::DisplayInfo* out) {
|
||||||
|
return (int)syscall1(montauk::abi::SYS_DISPLAYINFO, (uint64_t)out);
|
||||||
|
}
|
||||||
|
inline int display_modes(montauk::abi::DisplayModeInfo* out, int maxCount) {
|
||||||
|
return (int)syscall2(montauk::abi::SYS_DISPLAYMODES,
|
||||||
|
(uint64_t)out, (uint64_t)maxCount);
|
||||||
|
}
|
||||||
|
inline int display_set_mode(int modeIndex) {
|
||||||
|
return (int)syscall1(montauk::abi::SYS_DISPLAYSETMODE,
|
||||||
|
(uint64_t)modeIndex);
|
||||||
|
}
|
||||||
|
inline int display_brightness(int percent = -1) {
|
||||||
|
return (int)syscall1(montauk::abi::SYS_DISPLAYBRIGHTNESS,
|
||||||
|
(uint64_t)(int64_t)percent);
|
||||||
|
}
|
||||||
|
|
||||||
// Arguments
|
// Arguments
|
||||||
inline int getargs(char* buf, uint64_t maxLen) {
|
inline int getargs(char* buf, uint64_t maxLen) {
|
||||||
return (int)syscall2(montauk::abi::SYS_GETARGS, (uint64_t)buf, maxLen);
|
return (int)syscall2(montauk::abi::SYS_GETARGS, (uint64_t)buf, maxLen);
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Makefile for display (Display and Graphics settings applet) on MontaukOS
|
||||||
|
# Copyright (c) 2026 Daniel Hammer
|
||||||
|
|
||||||
|
MAKEFLAGS += -rR
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
|
||||||
|
ifeq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||||
|
CXX = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
|
||||||
|
else
|
||||||
|
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||||
|
endif
|
||||||
|
|
||||||
|
PROG_INC := ../../include
|
||||||
|
LINK_LD := ../../link.ld
|
||||||
|
BINDIR := ../../bin
|
||||||
|
OBJDIR := obj
|
||||||
|
LIBDIR := ../../lib
|
||||||
|
|
||||||
|
CXXFLAGS := \
|
||||||
|
-std=gnu++20 \
|
||||||
|
-g -O2 -pipe \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Wno-unused-parameter \
|
||||||
|
-Wno-unused-function \
|
||||||
|
-ffreestanding \
|
||||||
|
-fno-stack-protector \
|
||||||
|
-fno-stack-check \
|
||||||
|
-fno-rtti \
|
||||||
|
-fno-exceptions \
|
||||||
|
-ffunction-sections \
|
||||||
|
-fdata-sections \
|
||||||
|
-msse \
|
||||||
|
-msse2 \
|
||||||
|
-MMD -MP \
|
||||||
|
-I $(PROG_INC) \
|
||||||
|
-isystem $(PROG_INC)/libc
|
||||||
|
|
||||||
|
LDFLAGS := \
|
||||||
|
-nostdlib \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-T $(LINK_LD)
|
||||||
|
|
||||||
|
SRCS := main.cpp stb_truetype_impl.cpp
|
||||||
|
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||||
|
|
||||||
|
TARGET := $(BINDIR)/apps/display/display.elf
|
||||||
|
LIBS := $(LIBDIR)/libc/liblibc.a
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBS)
|
||||||
|
mkdir -p $(BINDIR)/apps/display
|
||||||
|
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(OBJDIR)/%.o: %.cpp Makefile
|
||||||
|
mkdir -p $(dir $@)
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
-include $(OBJS:.o=.d)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(OBJDIR) $(TARGET)
|
||||||
@@ -0,0 +1,661 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
* MontaukOS Display and Graphics settings
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <montauk/syscall.h>
|
||||||
|
#include <montauk/string.h>
|
||||||
|
#include <montauk/config.h>
|
||||||
|
#include <gui/mtk.hpp>
|
||||||
|
#include <gui/mtk/settings.hpp>
|
||||||
|
#include <gui/standalone.hpp>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <stdio.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace gui;
|
||||||
|
|
||||||
|
static constexpr int WIN_W = 620;
|
||||||
|
static constexpr int WIN_H = 500;
|
||||||
|
static constexpr int TAB_H = 36;
|
||||||
|
static constexpr int FOOTER_H = 44;
|
||||||
|
static constexpr int PAD = 18;
|
||||||
|
static constexpr int GAP = 12;
|
||||||
|
static constexpr int BUTTON_W = 88;
|
||||||
|
static constexpr int SLIDER_H = 6;
|
||||||
|
static constexpr int KNOB_R = 8;
|
||||||
|
static constexpr int MODE_ROW_H = 36;
|
||||||
|
static constexpr int VISIBLE_MODES = 6;
|
||||||
|
static constexpr int MAX_MODES = 32;
|
||||||
|
static constexpr int INFO_LABEL_W = 175;
|
||||||
|
|
||||||
|
enum Tab {
|
||||||
|
TAB_DISPLAY = 0,
|
||||||
|
TAB_GRAPHICS = 1,
|
||||||
|
TAB_COUNT = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* const kTabLabels[TAB_COUNT] = {
|
||||||
|
"Display",
|
||||||
|
"Graphics",
|
||||||
|
};
|
||||||
|
|
||||||
|
static WsWindow g_win;
|
||||||
|
static Tab g_tab = TAB_DISPLAY;
|
||||||
|
static int g_mouse_x = -1;
|
||||||
|
static int g_mouse_y = -1;
|
||||||
|
static bool g_brightness_drag = false;
|
||||||
|
static Color g_accent = colors::ACCENT;
|
||||||
|
|
||||||
|
static montauk::abi::DisplayInfo g_info = {};
|
||||||
|
static montauk::abi::DisplayModeInfo g_modes[MAX_MODES] = {};
|
||||||
|
static int g_mode_count = 0;
|
||||||
|
static bool g_driver_available = false;
|
||||||
|
static int g_selected_mode = -1;
|
||||||
|
static int g_pending_brightness = -1;
|
||||||
|
static bool g_tear_free = true;
|
||||||
|
static bool g_saved_tear_free = true;
|
||||||
|
static int g_mode_scroll = 0;
|
||||||
|
static char g_status[128] = {};
|
||||||
|
static uint64_t g_status_time = 0;
|
||||||
|
static uint64_t g_last_refresh = 0;
|
||||||
|
|
||||||
|
static mtk::Theme app_theme() {
|
||||||
|
mtk::Theme theme = mtk::make_theme(g_accent);
|
||||||
|
theme.danger_soft = Color::from_rgb(0xF8, 0xE0, 0xE0);
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect tab_bar_rect() {
|
||||||
|
return {0, 0, g_win.width, TAB_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect footer_rect() {
|
||||||
|
return {0, g_win.height - FOOTER_H, g_win.width, FOOTER_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int display_heading_y() {
|
||||||
|
return TAB_H + 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brightness_row_y() {
|
||||||
|
return display_heading_y() + system_font_height() + 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect brightness_slider_rect() {
|
||||||
|
int y = brightness_row_y() + (system_font_height() - SLIDER_H) / 2;
|
||||||
|
return {PAD + INFO_LABEL_W, y,
|
||||||
|
g_win.width - PAD * 2 - INFO_LABEL_W - 44, SLIDER_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int modes_separator_y() {
|
||||||
|
return brightness_row_y() + system_font_height() + 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int modes_header_y() {
|
||||||
|
return modes_separator_y() + 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mode_list_y() {
|
||||||
|
return modes_header_y() + system_font_height() + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect mode_row_rect(int visibleIndex) {
|
||||||
|
return {PAD, mode_list_y() + visibleIndex * MODE_ROW_H,
|
||||||
|
g_win.width - PAD * 2, MODE_ROW_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect apply_button() {
|
||||||
|
Rect foot = footer_rect();
|
||||||
|
return {foot.x + foot.w - PAD - BUTTON_W, foot.y + 7, BUTTON_W, 30};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect revert_button() {
|
||||||
|
Rect apply = apply_button();
|
||||||
|
return {apply.x - GAP - BUTTON_W, apply.y, BUTTON_W, apply.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect refresh_button() {
|
||||||
|
Rect apply = apply_button();
|
||||||
|
return {apply.x - GAP - BUTTON_W, apply.y, BUTTON_W, apply.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
static int graphics_table_y() {
|
||||||
|
return TAB_H + 24 + system_font_height() + 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int graphics_row_step() {
|
||||||
|
return system_font_height() + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int graphics_separator_y() {
|
||||||
|
return graphics_table_y() + graphics_row_step() * 5 + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int graphics_section_y() {
|
||||||
|
return graphics_separator_y() + 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect tear_free_option() {
|
||||||
|
return {PAD, graphics_section_y() + system_font_height() + 8,
|
||||||
|
g_win.width - PAD * 2, 24};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mouse_in(const Rect& rect) {
|
||||||
|
return rect.contains(g_mouse_x, g_mouse_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static mtk::WidgetState button_state(const Rect& rect, bool enabled = true) {
|
||||||
|
return mtk::widget_state(false, mouse_in(rect), enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_status(const char* msg) {
|
||||||
|
snprintf(g_status, sizeof(g_status), "%s", msg ? msg : "");
|
||||||
|
g_status_time = montauk::get_milliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool status_visible() {
|
||||||
|
return g_status[0] && montauk::get_milliseconds() - g_status_time < 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_graphics_preference() {
|
||||||
|
char user[64] = {};
|
||||||
|
if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) {
|
||||||
|
auto doc = montauk::config::load_user(user, "display");
|
||||||
|
g_tear_free = doc.get_bool("graphics.tear_free", true);
|
||||||
|
doc.destroy();
|
||||||
|
} else {
|
||||||
|
auto doc = montauk::config::load("display");
|
||||||
|
g_tear_free = doc.get_bool("graphics.tear_free", true);
|
||||||
|
doc.destroy();
|
||||||
|
}
|
||||||
|
g_saved_tear_free = g_tear_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool save_graphics_preference() {
|
||||||
|
montauk::toml::Doc doc;
|
||||||
|
doc.init();
|
||||||
|
montauk::config::set_bool(&doc, "graphics.tear_free", g_tear_free);
|
||||||
|
|
||||||
|
char user[64] = {};
|
||||||
|
int result;
|
||||||
|
if (montauk::getuser(user, sizeof(user)) > 0 && user[0])
|
||||||
|
result = montauk::config::save_user(user, "display", &doc);
|
||||||
|
else
|
||||||
|
result = montauk::config::save("display", &doc);
|
||||||
|
doc.destroy();
|
||||||
|
if (result >= 0) g_saved_tear_free = g_tear_free;
|
||||||
|
return result >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clamp_mode_scroll() {
|
||||||
|
int max_scroll = g_mode_count > VISIBLE_MODES
|
||||||
|
? g_mode_count - VISIBLE_MODES : 0;
|
||||||
|
if (g_mode_scroll < 0) g_mode_scroll = 0;
|
||||||
|
if (g_mode_scroll > max_scroll) g_mode_scroll = max_scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_state(bool resetPending) {
|
||||||
|
montauk::abi::DisplayInfo next = {};
|
||||||
|
if (montauk::display_info(&next) < 0) {
|
||||||
|
g_driver_available = false;
|
||||||
|
g_mode_count = 0;
|
||||||
|
g_last_refresh = montauk::get_milliseconds();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_info = next;
|
||||||
|
int count = montauk::display_modes(g_modes, MAX_MODES);
|
||||||
|
g_mode_count = count > 0 ? count : 0;
|
||||||
|
g_driver_available = true;
|
||||||
|
if (resetPending) {
|
||||||
|
g_selected_mode = g_info.currentMode;
|
||||||
|
g_pending_brightness = g_info.brightness;
|
||||||
|
if (g_selected_mode >= VISIBLE_MODES)
|
||||||
|
g_mode_scroll = g_selected_mode - VISIBLE_MODES + 1;
|
||||||
|
}
|
||||||
|
clamp_mode_scroll();
|
||||||
|
g_last_refresh = montauk::get_milliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dirty() {
|
||||||
|
bool display_dirty = g_driver_available
|
||||||
|
&& (g_selected_mode != g_info.currentMode
|
||||||
|
|| (g_info.brightness >= 0
|
||||||
|
&& g_pending_brightness != g_info.brightness));
|
||||||
|
return display_dirty || g_tear_free != g_saved_tear_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format_mode(char* out, int cap,
|
||||||
|
const montauk::abi::DisplayModeInfo& mode) {
|
||||||
|
uint32_t hz = (mode.refreshMilliHz + 500) / 1000;
|
||||||
|
snprintf(out, cap, "%ux%u %u Hz",
|
||||||
|
(unsigned)mode.width, (unsigned)mode.height, (unsigned)hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fit_text(char* out, int outCap, const char* text, int maxWidth) {
|
||||||
|
if (!out || outCap <= 0) return;
|
||||||
|
out[0] = '\0';
|
||||||
|
if (!text || !text[0] || maxWidth <= 0) return;
|
||||||
|
if (text_width(text) <= maxWidth) {
|
||||||
|
snprintf(out, outCap, "%s", text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* ellipsis = "...";
|
||||||
|
int len = 0;
|
||||||
|
while (text[len] && len < outCap - 4) {
|
||||||
|
out[len] = text[len];
|
||||||
|
out[len + 1] = '\0';
|
||||||
|
if (text_width(out) + text_width(ellipsis) > maxWidth) break;
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
out[len++] = '.';
|
||||||
|
out[len++] = '.';
|
||||||
|
out[len++] = '.';
|
||||||
|
out[len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_text_fit(Canvas& c, int x, int y, const char* text,
|
||||||
|
int maxWidth, Color color) {
|
||||||
|
char fitted[160];
|
||||||
|
fit_text(fitted, sizeof(fitted), text, maxWidth);
|
||||||
|
c.text(x, y, fitted, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_kv(Canvas& c, int* y, const char* key, const char* value,
|
||||||
|
const mtk::Theme& theme) {
|
||||||
|
c.text(PAD, *y, key, theme.text_muted);
|
||||||
|
draw_text_fit(c, PAD + INFO_LABEL_W, *y, value,
|
||||||
|
g_win.width - PAD * 2 - INFO_LABEL_W, theme.text);
|
||||||
|
*y += graphics_row_step();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_slider(Canvas& c, const Rect& rect, int value,
|
||||||
|
const mtk::Theme& theme) {
|
||||||
|
Color track = mtk::mix(theme.surface, theme.border, 80);
|
||||||
|
c.fill_rounded_rect(rect.x, rect.y, rect.w, rect.h, rect.h / 2, track);
|
||||||
|
int fill_w = (value * rect.w) / 100;
|
||||||
|
if (fill_w > 0)
|
||||||
|
c.fill_rounded_rect(rect.x, rect.y, fill_w, rect.h,
|
||||||
|
rect.h / 2, theme.accent);
|
||||||
|
int kx = rect.x + fill_w;
|
||||||
|
int ky = rect.y + rect.h / 2;
|
||||||
|
fill_circle(c, kx, ky, KNOB_R, theme.accent);
|
||||||
|
fill_circle(c, kx, ky, KNOB_R - 3, colors::WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_display_tab(Canvas& c, const mtk::Theme& theme) {
|
||||||
|
int y = display_heading_y();
|
||||||
|
if (!g_driver_available) {
|
||||||
|
c.text(PAD, y, "Intel display driver unavailable", theme.danger);
|
||||||
|
c.text(PAD, y + 28,
|
||||||
|
"The firmware framebuffer remains active, but hardware controls are unavailable.",
|
||||||
|
theme.text_subtle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char headline[128];
|
||||||
|
snprintf(headline, sizeof(headline), "%s on %s",
|
||||||
|
g_info.monitorName[0] ? g_info.monitorName : "Display",
|
||||||
|
g_info.connectorName[0] ? g_info.connectorName : "unknown connector");
|
||||||
|
draw_text_fit(c, PAD, y, headline, g_win.width - PAD * 2,
|
||||||
|
g_info.connected ? theme.text : theme.danger);
|
||||||
|
|
||||||
|
y = brightness_row_y();
|
||||||
|
c.text(PAD, y, "Brightness", theme.text_subtle);
|
||||||
|
if (g_info.brightness >= 0) {
|
||||||
|
Rect slider = brightness_slider_rect();
|
||||||
|
draw_slider(c, slider, g_pending_brightness, theme);
|
||||||
|
char percent[16];
|
||||||
|
snprintf(percent, sizeof(percent), "%d%%", g_pending_brightness);
|
||||||
|
c.text(slider.x + slider.w + 14, slider.y - 6, percent, theme.text);
|
||||||
|
} else {
|
||||||
|
draw_text_fit(c, PAD + INFO_LABEL_W, y,
|
||||||
|
"Controlled by the external display",
|
||||||
|
g_win.width - PAD * 2 - INFO_LABEL_W, theme.text_muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
y = modes_separator_y();
|
||||||
|
mtk::draw_separator(c, PAD, y, g_win.width - PAD * 2, theme);
|
||||||
|
y = modes_header_y();
|
||||||
|
c.text(PAD, y, "AVAILABLE MODES", theme.text_muted);
|
||||||
|
const char* hint = "Grey modes require link training";
|
||||||
|
c.text(g_win.width - PAD - text_width(hint), y, hint, theme.text_muted);
|
||||||
|
|
||||||
|
for (int row = 0; row < VISIBLE_MODES; row++) {
|
||||||
|
int index = g_mode_scroll + row;
|
||||||
|
if (index >= g_mode_count) break;
|
||||||
|
Rect option = mode_row_rect(row);
|
||||||
|
bool valid = (g_modes[index].flags
|
||||||
|
& montauk::abi::DISPLAY_MODE_DRIVER_VALID) != 0;
|
||||||
|
bool selected = index == g_selected_mode;
|
||||||
|
if (selected)
|
||||||
|
c.fill_rounded_rect(option.x, option.y + 2, option.w, option.h - 4,
|
||||||
|
4, theme.accent_soft);
|
||||||
|
Rect radio = {option.x + 4, option.y, 18, option.h};
|
||||||
|
mtk::draw_radio(c, radio, "", selected, theme);
|
||||||
|
|
||||||
|
char mode_text[64];
|
||||||
|
format_mode(mode_text, sizeof(mode_text), g_modes[index]);
|
||||||
|
Color text = valid ? theme.text : theme.text_muted;
|
||||||
|
int textY = option.y + (option.h - system_font_height()) / 2;
|
||||||
|
c.text(option.x + 34, textY, mode_text, text);
|
||||||
|
|
||||||
|
const char* state = nullptr;
|
||||||
|
Color stateColor = theme.text_subtle;
|
||||||
|
if (g_modes[index].flags & montauk::abi::DISPLAY_MODE_CURRENT) {
|
||||||
|
state = "Current";
|
||||||
|
stateColor = theme.accent;
|
||||||
|
} else if (g_modes[index].flags & montauk::abi::DISPLAY_MODE_PREFERRED) {
|
||||||
|
state = "Preferred";
|
||||||
|
} else if (!valid) {
|
||||||
|
state = "Link training required";
|
||||||
|
stateColor = theme.text_muted;
|
||||||
|
}
|
||||||
|
if (state)
|
||||||
|
c.text(option.x + option.w - text_width(state) - 10,
|
||||||
|
textY, state, stateColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_mode_count == 0) {
|
||||||
|
c.text(PAD + 4, mode_list_y() + 9,
|
||||||
|
"No EDID modes were reported; the firmware mode remains active.",
|
||||||
|
theme.text_subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_mode_count > VISIBLE_MODES) {
|
||||||
|
char page[48];
|
||||||
|
snprintf(page, sizeof(page), "Modes %d-%d of %d (scroll)",
|
||||||
|
g_mode_scroll + 1,
|
||||||
|
g_mode_scroll + VISIBLE_MODES < g_mode_count
|
||||||
|
? g_mode_scroll + VISIBLE_MODES : g_mode_count,
|
||||||
|
g_mode_count);
|
||||||
|
c.text(PAD, footer_rect().y - 22, page, theme.text_muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* yes_no(bool value) {
|
||||||
|
return value ? "Available" : "Unavailable";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_graphics_tab(Canvas& c, const mtk::Theme& theme) {
|
||||||
|
int y = TAB_H + 24;
|
||||||
|
c.text(PAD, y, "Intel Graphics", theme.text);
|
||||||
|
y = graphics_table_y();
|
||||||
|
|
||||||
|
if (!g_driver_available) {
|
||||||
|
c.text(PAD, y, "No managed Intel GPU was detected.", theme.danger);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char device[96];
|
||||||
|
snprintf(device, sizeof(device), "%s (Gen %u, PCI %04X)",
|
||||||
|
g_info.gpuName, (unsigned)g_info.generation,
|
||||||
|
(unsigned)g_info.deviceId);
|
||||||
|
draw_kv(c, &y, "Adapter", device, theme);
|
||||||
|
draw_kv(c, &y, "EDID modes",
|
||||||
|
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_EDID) != 0),
|
||||||
|
theme);
|
||||||
|
draw_kv(c, &y, "Page flipping",
|
||||||
|
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_PAGE_FLIP) != 0),
|
||||||
|
theme);
|
||||||
|
draw_kv(c, &y, "VBlank interrupt",
|
||||||
|
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_VBLANK_IRQ) != 0),
|
||||||
|
theme);
|
||||||
|
draw_kv(c, &y, "Safe mode switch",
|
||||||
|
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_SAFE_SWITCH) != 0),
|
||||||
|
theme);
|
||||||
|
|
||||||
|
y = graphics_separator_y();
|
||||||
|
mtk::draw_separator(c, PAD, y, g_win.width - PAD * 2, theme);
|
||||||
|
y = graphics_section_y();
|
||||||
|
c.text(PAD, y, "PRESENTATION", theme.text_muted);
|
||||||
|
|
||||||
|
Rect option = tear_free_option();
|
||||||
|
bool flipAvailable =
|
||||||
|
(g_info.capabilities & montauk::abi::DISPLAY_CAP_PAGE_FLIP) != 0;
|
||||||
|
mtk::draw_checkbox(c, option, "Prefer tear-free hardware page flipping",
|
||||||
|
mtk::check_state(g_tear_free), theme,
|
||||||
|
flipAvailable, mouse_in(option));
|
||||||
|
c.text(option.x + 24, option.y + option.h + 2,
|
||||||
|
flipAvailable
|
||||||
|
? "Applied when the next desktop session starts."
|
||||||
|
: "Unavailable on this boot; the software copy path is active.",
|
||||||
|
flipAvailable ? theme.text_subtle : theme.text_muted);
|
||||||
|
|
||||||
|
int safetySeparator = option.y + option.h + system_font_height() + 18;
|
||||||
|
mtk::draw_separator(c, PAD, safetySeparator, g_win.width - PAD * 2, theme);
|
||||||
|
c.text(PAD, safetySeparator + 16, "MODESETTING SAFETY", theme.text_muted);
|
||||||
|
c.text(PAD, safetySeparator + 16 + system_font_height() + 8,
|
||||||
|
"The driver keeps the firmware-trained display link and only exposes modes",
|
||||||
|
theme.text_subtle);
|
||||||
|
c.text(PAD, safetySeparator + 16 + system_font_height() * 2 + 10,
|
||||||
|
"that can be changed without unsafe PLL or DisplayPort retraining.",
|
||||||
|
theme.text_subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_footer(Canvas& c, const mtk::Theme& theme) {
|
||||||
|
Rect foot = footer_rect();
|
||||||
|
c.fill_rect(foot.x, foot.y, foot.w, foot.h, theme.surface);
|
||||||
|
mtk::draw_separator(c, 0, foot.y, g_win.width, theme);
|
||||||
|
|
||||||
|
const char* message = status_visible()
|
||||||
|
? g_status : (dirty() ? "Unsaved display changes" : "Display settings ready");
|
||||||
|
c.text(PAD, foot.y + (foot.h - system_font_height()) / 2,
|
||||||
|
message, dirty() ? theme.text : theme.text_subtle);
|
||||||
|
|
||||||
|
if (g_tab == TAB_DISPLAY || dirty()) {
|
||||||
|
Rect revert = revert_button();
|
||||||
|
Rect apply = apply_button();
|
||||||
|
mtk::draw_button(c, revert, "Revert", mtk::BUTTON_SECONDARY,
|
||||||
|
button_state(revert, dirty()), theme);
|
||||||
|
mtk::draw_button(c, apply, "Apply", mtk::BUTTON_PRIMARY,
|
||||||
|
button_state(apply, dirty()), theme);
|
||||||
|
} else {
|
||||||
|
Rect refresh = refresh_button();
|
||||||
|
mtk::draw_button(c, refresh, "Refresh", mtk::BUTTON_SECONDARY,
|
||||||
|
button_state(refresh), theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render() {
|
||||||
|
mtk::StandaloneHost host(&g_win);
|
||||||
|
Canvas c = host.canvas();
|
||||||
|
mtk::Theme theme = app_theme();
|
||||||
|
c.fill(theme.window_bg);
|
||||||
|
mtk::draw_tab_bar(c, tab_bar_rect(), kTabLabels, TAB_COUNT, (int)g_tab, theme);
|
||||||
|
if (g_tab == TAB_DISPLAY) draw_display_tab(c, theme);
|
||||||
|
else draw_graphics_tab(c, theme);
|
||||||
|
draw_footer(c, theme);
|
||||||
|
host.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int brightness_from_x(int mx) {
|
||||||
|
Rect slider = brightness_slider_rect();
|
||||||
|
int value = ((mx - slider.x) * 100) / slider.w;
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
if (value > 100) value = 100;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool slider_hit(int mx, int my) {
|
||||||
|
Rect slider = brightness_slider_rect();
|
||||||
|
return mx >= slider.x - KNOB_R && mx <= slider.x + slider.w + KNOB_R
|
||||||
|
&& my >= slider.y - KNOB_R - 4 && my <= slider.y + slider.h + KNOB_R + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void revert_changes() {
|
||||||
|
g_selected_mode = g_info.currentMode;
|
||||||
|
g_pending_brightness = g_info.brightness;
|
||||||
|
g_tear_free = g_saved_tear_free;
|
||||||
|
set_status("Changes reverted");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void apply_changes() {
|
||||||
|
if (!dirty()) return;
|
||||||
|
bool graphics_changed = g_tear_free != g_saved_tear_free;
|
||||||
|
|
||||||
|
if (g_driver_available && g_info.brightness >= 0
|
||||||
|
&& g_pending_brightness != g_info.brightness) {
|
||||||
|
int result = montauk::display_brightness(g_pending_brightness);
|
||||||
|
if (result < 0) {
|
||||||
|
set_status("Could not change panel brightness");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_driver_available && g_selected_mode != g_info.currentMode) {
|
||||||
|
int result = montauk::display_set_mode(g_selected_mode);
|
||||||
|
if (result < 0) {
|
||||||
|
set_status(result == -2
|
||||||
|
? "Mode needs unsupported display-link training"
|
||||||
|
: "Mode change failed; previous mode restored");
|
||||||
|
refresh_state(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphics_changed && !save_graphics_preference()) {
|
||||||
|
set_status("Could not save graphics preference");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_state(true);
|
||||||
|
set_status(graphics_changed
|
||||||
|
? "Saved; takes effect next desktop session"
|
||||||
|
: "Display settings applied");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_mouse(const montauk::abi::WinEvent& ev) {
|
||||||
|
int mx = ev.mouse.x;
|
||||||
|
int my = ev.mouse.y;
|
||||||
|
uint8_t buttons = ev.mouse.buttons;
|
||||||
|
uint8_t previous = ev.mouse.prev_buttons;
|
||||||
|
bool pressed = (buttons & 1) && !(previous & 1);
|
||||||
|
bool released = !(buttons & 1) && (previous & 1);
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my);
|
||||||
|
if (tab >= 0) {
|
||||||
|
g_tab = (Tab)tab;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_tab == TAB_DISPLAY && g_info.brightness >= 0) {
|
||||||
|
if (pressed && slider_hit(mx, my)) g_brightness_drag = true;
|
||||||
|
if (g_brightness_drag && (buttons & 1))
|
||||||
|
g_pending_brightness = brightness_from_x(mx);
|
||||||
|
if (released) g_brightness_drag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_tab == TAB_DISPLAY && ev.mouse.scroll != 0) {
|
||||||
|
g_mode_scroll += ev.mouse.scroll > 0 ? -1 : 1;
|
||||||
|
clamp_mode_scroll();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pressed) return g_brightness_drag;
|
||||||
|
|
||||||
|
if (g_tab == TAB_DISPLAY) {
|
||||||
|
for (int row = 0; row < VISIBLE_MODES; row++) {
|
||||||
|
int index = g_mode_scroll + row;
|
||||||
|
if (index >= g_mode_count) break;
|
||||||
|
if (mode_row_rect(row).contains(mx, my)
|
||||||
|
&& (g_modes[index].flags
|
||||||
|
& montauk::abi::DISPLAY_MODE_DRIVER_VALID)) {
|
||||||
|
g_selected_mode = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (tear_free_option().contains(mx, my)
|
||||||
|
&& (g_info.capabilities
|
||||||
|
& montauk::abi::DISPLAY_CAP_PAGE_FLIP)) {
|
||||||
|
g_tear_free = !g_tear_free;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (revert_button().contains(mx, my) && dirty()) {
|
||||||
|
revert_changes();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (apply_button().contains(mx, my) && dirty()) {
|
||||||
|
apply_changes();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (refresh_button().contains(mx, my) && !dirty()) {
|
||||||
|
refresh_state(true);
|
||||||
|
set_status("Display status refreshed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_key(const montauk::abi::KeyEvent& key) {
|
||||||
|
if (!key.pressed) return false;
|
||||||
|
if (key.scancode == 0x01) {
|
||||||
|
g_win.closed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key.ascii == '\t') {
|
||||||
|
g_tab = g_tab == TAB_DISPLAY ? TAB_GRAPHICS : TAB_DISPLAY;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||||
|
apply_changes();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key.ascii == 'r' || key.ascii == 'R') {
|
||||||
|
refresh_state(!dirty());
|
||||||
|
set_status("Display status refreshed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void _start() {
|
||||||
|
if (!fonts::init()) montauk::exit(1);
|
||||||
|
g_accent = mtk::load_system_accent();
|
||||||
|
load_graphics_preference();
|
||||||
|
|
||||||
|
if (!g_win.create("Display", WIN_W, WIN_H)) montauk::exit(1);
|
||||||
|
refresh_state(true);
|
||||||
|
render();
|
||||||
|
|
||||||
|
while (g_win.id >= 0 && !g_win.closed) {
|
||||||
|
montauk::abi::WinEvent ev;
|
||||||
|
int result = g_win.poll(&ev);
|
||||||
|
bool redraw = false;
|
||||||
|
if (result < 0) break;
|
||||||
|
if (result == 0) {
|
||||||
|
uint64_t now = montauk::get_milliseconds();
|
||||||
|
if (!dirty() && now - g_last_refresh >= 3000) {
|
||||||
|
refresh_state(true);
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
if (status_visible()) redraw = true;
|
||||||
|
if (redraw) render();
|
||||||
|
montauk::sleep_ms(16);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.type == 3) break;
|
||||||
|
if (ev.type == 2 || ev.type == 4) {
|
||||||
|
redraw = true;
|
||||||
|
} else if (ev.type == 1) {
|
||||||
|
g_mouse_x = ev.mouse.x;
|
||||||
|
g_mouse_y = ev.mouse.y;
|
||||||
|
redraw = true;
|
||||||
|
handle_mouse(ev);
|
||||||
|
} else if (ev.type == 0) {
|
||||||
|
redraw = handle_key(ev.key);
|
||||||
|
}
|
||||||
|
if (redraw) render();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_win.destroy();
|
||||||
|
montauk::exit(0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[app]
|
||||||
|
id = "display"
|
||||||
|
name = "Display"
|
||||||
|
binary = "display.elf"
|
||||||
|
icon = "preferences-desktop-display.svg"
|
||||||
|
|
||||||
|
[menu]
|
||||||
|
category = "System"
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[desktop]
|
||||||
|
section = "settings"
|
||||||
|
admin_only = false
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* stb_truetype_impl.cpp
|
||||||
|
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <montauk/heap.h>
|
||||||
|
#include <montauk/string.h>
|
||||||
|
#include <gui/stb_math.h>
|
||||||
|
|
||||||
|
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||||
|
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||||
|
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||||
|
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||||
|
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||||
|
#define STBTT_cos(x) stb_cos(x)
|
||||||
|
#define STBTT_acos(x) stb_acos(x)
|
||||||
|
#define STBTT_fabs(x) stb_fabs(x)
|
||||||
|
|
||||||
|
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
|
||||||
|
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
|
||||||
|
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
|
||||||
|
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
|
||||||
|
#define STBTT_strlen(x) montauk::slen(x)
|
||||||
|
#define STBTT_assert(x) ((void)(x))
|
||||||
|
|
||||||
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
|
#include <gui/stb_truetype.h>
|
||||||
@@ -32,6 +32,7 @@ APPS=(
|
|||||||
"video|apps/scalable/video-player.svg"
|
"video|apps/scalable/video-player.svg"
|
||||||
"bluetooth|apps/scalable/bluetooth.svg"
|
"bluetooth|apps/scalable/bluetooth.svg"
|
||||||
"network|devices/scalable/network-wired.svg"
|
"network|devices/scalable/network-wired.svg"
|
||||||
|
"display|apps/scalable/preferences-desktop-display.svg"
|
||||||
"terminal|apps/scalable/utilities-terminal.svg"
|
"terminal|apps/scalable/utilities-terminal.svg"
|
||||||
"klog|apps/scalable/utilities-terminal.svg"
|
"klog|apps/scalable/utilities-terminal.svg"
|
||||||
"procmgr|apps/scalable/system-monitor.svg"
|
"procmgr|apps/scalable/system-monitor.svg"
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t SYS_SDR_SETPARAM = 147; // (handle, param, value)
|
static constexpr uint64_t SYS_SDR_SETPARAM = 147; // (handle, param, value)
|
||||||
static constexpr uint64_t SYS_SDR_GETPARAM = 148; // (handle, param) -> value
|
static constexpr uint64_t SYS_SDR_GETPARAM = 148; // (handle, param) -> value
|
||||||
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
static constexpr uint64_t SYS_SETUNIXTIME = 153;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYINFO = 154;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYMODES = 155;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
|
||||||
|
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
|
||||||
|
|
||||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||||
@@ -282,6 +286,51 @@ namespace montauk::abi {
|
|||||||
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DisplayConnectorType : uint8_t {
|
||||||
|
DISPLAY_CONNECTOR_UNKNOWN = 0,
|
||||||
|
DISPLAY_CONNECTOR_EDP = 1,
|
||||||
|
DISPLAY_CONNECTOR_DP = 2,
|
||||||
|
DISPLAY_CONNECTOR_HDMI = 3,
|
||||||
|
DISPLAY_CONNECTOR_DVI = 4,
|
||||||
|
DISPLAY_CONNECTOR_VGA = 5,
|
||||||
|
DISPLAY_CONNECTOR_LVDS = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_MODESET = 1u << 0;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_EDID = 1u << 1;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_BRIGHTNESS = 1u << 2;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_PAGE_FLIP = 1u << 3;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_VBLANK_IRQ = 1u << 4;
|
||||||
|
static constexpr uint32_t DISPLAY_CAP_SAFE_SWITCH = 1u << 5;
|
||||||
|
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_PREFERRED = 1u << 0;
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_CURRENT = 1u << 1;
|
||||||
|
static constexpr uint32_t DISPLAY_MODE_DRIVER_VALID = 1u << 2;
|
||||||
|
|
||||||
|
struct DisplayModeInfo {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t refreshMilliHz;
|
||||||
|
uint32_t pixelClockKHz;
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t _pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DisplayInfo {
|
||||||
|
uint32_t capabilities;
|
||||||
|
uint32_t modeCount;
|
||||||
|
int32_t currentMode;
|
||||||
|
int32_t brightness;
|
||||||
|
uint16_t deviceId;
|
||||||
|
uint8_t generation;
|
||||||
|
uint8_t connectorType;
|
||||||
|
uint8_t connected;
|
||||||
|
uint8_t _pad0[3];
|
||||||
|
char gpuName[64];
|
||||||
|
char connectorName[32];
|
||||||
|
char monitorName[64];
|
||||||
|
};
|
||||||
|
|
||||||
struct SysInfo {
|
struct SysInfo {
|
||||||
char osName[32];
|
char osName[32];
|
||||||
char osVersion[32];
|
char osVersion[32];
|
||||||
|
|||||||
@@ -326,6 +326,21 @@ namespace montauk {
|
|||||||
// Framebuffer
|
// Framebuffer
|
||||||
inline void fb_info(montauk::abi::FbInfo* info) { syscall1(montauk::abi::SYS_FBINFO, (uint64_t)info); }
|
inline void fb_info(montauk::abi::FbInfo* info) { syscall1(montauk::abi::SYS_FBINFO, (uint64_t)info); }
|
||||||
inline void* fb_map() { return (void*)syscall0(montauk::abi::SYS_FBMAP); }
|
inline void* fb_map() { return (void*)syscall0(montauk::abi::SYS_FBMAP); }
|
||||||
|
inline int display_info(montauk::abi::DisplayInfo* out) {
|
||||||
|
return (int)syscall1(montauk::abi::SYS_DISPLAYINFO, (uint64_t)out);
|
||||||
|
}
|
||||||
|
inline int display_modes(montauk::abi::DisplayModeInfo* out, int maxCount) {
|
||||||
|
return (int)syscall2(montauk::abi::SYS_DISPLAYMODES,
|
||||||
|
(uint64_t)out, (uint64_t)maxCount);
|
||||||
|
}
|
||||||
|
inline int display_set_mode(int modeIndex) {
|
||||||
|
return (int)syscall1(montauk::abi::SYS_DISPLAYSETMODE,
|
||||||
|
(uint64_t)modeIndex);
|
||||||
|
}
|
||||||
|
inline int display_brightness(int percent = -1) {
|
||||||
|
return (int)syscall1(montauk::abi::SYS_DISPLAYBRIGHTNESS,
|
||||||
|
(uint64_t)(int64_t)percent);
|
||||||
|
}
|
||||||
|
|
||||||
// Arguments
|
// Arguments
|
||||||
inline int getargs(char* buf, uint64_t maxLen) {
|
inline int getargs(char* buf, uint64_t maxLen) {
|
||||||
|
|||||||
Reference in New Issue
Block a user