feat: various power and thermal optimizations, fix Printers app regression
This commit is contained in:
@@ -735,6 +735,10 @@ namespace Drivers::Net::E1000E {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
bool RequiresPolling() {
|
||||
return g_initialized && g_pollingMode;
|
||||
}
|
||||
|
||||
void SetRxCallback(RxCallback callback) {
|
||||
g_rxCallback = callback;
|
||||
}
|
||||
|
||||
@@ -147,6 +147,9 @@ namespace Drivers::Net::E1000E {
|
||||
// Check if the device was found and initialized
|
||||
bool IsInitialized();
|
||||
|
||||
// Returns true only when the driver had to fall back to timer polling.
|
||||
bool RequiresPolling();
|
||||
|
||||
// RX callback type: called with (packet data, length)
|
||||
using RxCallback = void(*)(const uint8_t* data, uint16_t length);
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Drivers::USB::Xhci {
|
||||
|
||||
// Hot-plug deferred work
|
||||
static volatile bool g_hotplugPending[MAX_PORTS] = {};
|
||||
static volatile bool g_deferredWorkPending = false;
|
||||
static bool g_hotplugProcessing = false;
|
||||
|
||||
// MMIO region pointers
|
||||
@@ -302,6 +303,7 @@ namespace Drivers::USB::Xhci {
|
||||
// Defer enumeration to ProcessDeferredWork (called from timer tick)
|
||||
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
|
||||
g_hotplugPending[portId - 1] = true;
|
||||
g_deferredWorkPending = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -723,15 +725,20 @@ namespace Drivers::USB::Xhci {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
bool HasDeferredWork() {
|
||||
return g_initialized && g_deferredWorkPending;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ProcessDeferredWork - handle hot-plug outside interrupt context
|
||||
// Called from timer tick (same pattern as E1000E::Poll)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void ProcessDeferredWork() {
|
||||
if (!g_initialized || !g_bootScanComplete) return;
|
||||
if (!g_initialized || !g_bootScanComplete || !g_deferredWorkPending) return;
|
||||
if (g_hotplugProcessing) return;
|
||||
g_hotplugProcessing = true;
|
||||
g_deferredWorkPending = false;
|
||||
|
||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||
if (!g_hotplugPending[port]) continue;
|
||||
|
||||
@@ -292,6 +292,7 @@ namespace Drivers::USB::Xhci {
|
||||
void Initialize();
|
||||
bool Probe(const Pci::PciDevice& dev);
|
||||
bool IsInitialized();
|
||||
bool HasDeferredWork();
|
||||
|
||||
// Deferred hot-plug processing (call from timer tick, not interrupt context)
|
||||
void ProcessDeferredWork();
|
||||
|
||||
+2
-2
@@ -201,11 +201,11 @@ extern "C" void kmain() {
|
||||
if (bspCpu && bspCpu->hasMwait) {
|
||||
static volatile uint64_t s_bspIdleMonitor = 0;
|
||||
for (;;) {
|
||||
Hal::IdleWait(&s_bspIdleMonitor);
|
||||
Timekeeping::IdleOnce(true, &s_bspIdleMonitor);
|
||||
}
|
||||
} else {
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
Timekeeping::IdleOnce(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +443,46 @@ namespace Sched {
|
||||
schedLock.Release();
|
||||
}
|
||||
|
||||
bool HasReadyProcesses() {
|
||||
return readyCount > 0;
|
||||
}
|
||||
|
||||
void RunBspMaintenance() {
|
||||
schedLock.Acquire();
|
||||
uint64_t now = Timekeeping::GetTicks();
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Blocked &&
|
||||
processTable[i].sleepUntilTick != 0 &&
|
||||
now >= processTable[i].sleepUntilTick) {
|
||||
processTable[i].sleepUntilTick = 0;
|
||||
processTable[i].waitingForPid = -1;
|
||||
processTable[i].waitingOnObject = nullptr;
|
||||
processTable[i].state = ProcessState::Ready;
|
||||
readyCount++;
|
||||
}
|
||||
}
|
||||
schedLock.Release();
|
||||
|
||||
ReclaimTerminated();
|
||||
}
|
||||
|
||||
uint64_t GetNextDeadlineTick() {
|
||||
uint64_t nextDeadline = 0;
|
||||
|
||||
schedLock.Acquire();
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state != ProcessState::Blocked) continue;
|
||||
uint64_t deadline = processTable[i].sleepUntilTick;
|
||||
if (deadline == 0) continue;
|
||||
if (nextDeadline == 0 || deadline < nextDeadline) {
|
||||
nextDeadline = deadline;
|
||||
}
|
||||
}
|
||||
schedLock.Release();
|
||||
|
||||
return nextDeadline;
|
||||
}
|
||||
|
||||
void Schedule() {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
|
||||
@@ -532,28 +572,12 @@ namespace Sched {
|
||||
schedLock.Release();
|
||||
}
|
||||
|
||||
void Tick() {
|
||||
void Tick(uint32_t elapsedMs) {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
|
||||
// BSP: wake sleeping processes and reclaim terminated slots
|
||||
if (cpu->cpuIndex == 0) {
|
||||
schedLock.Acquire();
|
||||
uint64_t now = Timekeeping::GetTicks();
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Blocked &&
|
||||
processTable[i].sleepUntilTick != 0 &&
|
||||
now >= processTable[i].sleepUntilTick) {
|
||||
processTable[i].sleepUntilTick = 0;
|
||||
processTable[i].waitingForPid = -1;
|
||||
processTable[i].waitingOnObject = nullptr;
|
||||
processTable[i].state = ProcessState::Ready;
|
||||
readyCount++;
|
||||
}
|
||||
}
|
||||
schedLock.Release();
|
||||
|
||||
// Reclaim terminated process memory (BSP only, once per tick)
|
||||
ReclaimTerminated();
|
||||
RunBspMaintenance();
|
||||
}
|
||||
|
||||
int slot = cpu->currentSlot;
|
||||
@@ -577,8 +601,10 @@ namespace Sched {
|
||||
return;
|
||||
}
|
||||
|
||||
if (processTable[slot].sliceRemaining > 0) {
|
||||
processTable[slot].sliceRemaining--;
|
||||
if (processTable[slot].sliceRemaining > elapsedMs) {
|
||||
processTable[slot].sliceRemaining -= elapsedMs;
|
||||
} else {
|
||||
processTable[slot].sliceRemaining = 0;
|
||||
}
|
||||
|
||||
if (processTable[slot].sliceRemaining == 0) {
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Sched {
|
||||
uint64_t savedRsp;
|
||||
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
|
||||
uint64_t entryPoint;
|
||||
uint64_t sliceRemaining; // Ticks left in current time slice
|
||||
uint64_t sliceRemaining; // Milliseconds left in current time slice
|
||||
uint64_t pml4Phys; // Physical address of per-process PML4
|
||||
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
|
||||
uint64_t userStackTop; // User-space stack top
|
||||
@@ -87,8 +87,12 @@ namespace Sched {
|
||||
int Spawn(const char* vfsPath, const char* args = nullptr);
|
||||
void Schedule();
|
||||
|
||||
// Called from the APIC timer handler on every tick (per-CPU).
|
||||
void Tick();
|
||||
// True when there is runnable work somewhere in the process table.
|
||||
bool HasReadyProcesses();
|
||||
|
||||
// Called from the APIC timer handler with the elapsed time for that CPU's
|
||||
// tick interval. The BSP runs at 1 ms; APs may use a coarser interval.
|
||||
void Tick(uint32_t elapsedMs = 1);
|
||||
|
||||
// Get the PID of the currently running process (-1 if idle)
|
||||
int GetCurrentPid();
|
||||
@@ -112,6 +116,14 @@ namespace Sched {
|
||||
// timeoutMs == 0 means wait indefinitely.
|
||||
void BlockOnObject(void* object, uint64_t timeoutMs = 0);
|
||||
|
||||
// BSP-only scheduler housekeeping: wake expired sleepers and reclaim
|
||||
// terminated process resources.
|
||||
void RunBspMaintenance();
|
||||
|
||||
// Return the earliest blocked sleep/object timeout deadline in ticks,
|
||||
// or 0 when no timed waits are pending.
|
||||
uint64_t GetNextDeadlineTick();
|
||||
|
||||
// Wake any processes blocked on the given object.
|
||||
void WakeObjectWaiters(void* object);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <atomic>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
@@ -33,30 +34,90 @@ namespace Timekeeping {
|
||||
// APIC timer divide configuration values
|
||||
static constexpr uint32_t DIVIDE_BY_16 = 0x03;
|
||||
|
||||
// Timer tick rate: 1000 Hz (1 ms per tick)
|
||||
static constexpr uint32_t TIMER_HZ = 1000;
|
||||
// The BSP keeps a 1 ms tick for timekeeping and sleep deadlines.
|
||||
// APs use a coarser 10 ms scheduler tick to avoid waking idle cores
|
||||
// 1000 times per second with no useful work to do.
|
||||
static constexpr uint32_t BSP_TICK_INTERVAL_MS = 1;
|
||||
static constexpr uint32_t BSP_TIMER_HZ = 1000 / BSP_TICK_INTERVAL_MS;
|
||||
static constexpr uint32_t AP_TICK_INTERVAL_MS = 10;
|
||||
// Without a reschedule IPI, the BSP still needs a short periodic safety net
|
||||
// while idle so input- and IPC-driven wakeups do not feel laggy.
|
||||
static constexpr uint32_t BSP_IDLE_MAX_INTERVAL_MS = 2;
|
||||
|
||||
// Global state
|
||||
static std::atomic<uint64_t> g_tickCount{0};
|
||||
static uint32_t g_ticksPerMs = 0;
|
||||
|
||||
static bool g_schedEnabled = false;
|
||||
static volatile bool g_bspIdleOneShotArmed = false;
|
||||
static uint32_t g_bspIdleOneShotMs = 0;
|
||||
static uint32_t g_bspIdleInitialCount = 0;
|
||||
|
||||
// Timer IRQ handler: BSP handles timekeeping+polling, all CPUs run scheduler
|
||||
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
|
||||
return g_ticksPerMs * intervalMs;
|
||||
}
|
||||
|
||||
static void ProgramTimer(bool periodic, uint32_t intervalMs) {
|
||||
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER);
|
||||
if (periodic) {
|
||||
lvt |= LVT_PERIODIC;
|
||||
}
|
||||
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL,
|
||||
CountForIntervalMs(intervalMs));
|
||||
}
|
||||
|
||||
static void ProgramBspPeriodicTimer() {
|
||||
ProgramTimer(true, BSP_TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
static void ProgramBspIdleOneShotTimer(uint32_t intervalMs) {
|
||||
g_bspIdleOneShotMs = intervalMs;
|
||||
g_bspIdleInitialCount = CountForIntervalMs(intervalMs);
|
||||
g_bspIdleOneShotArmed = true;
|
||||
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT,
|
||||
(Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER));
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_bspIdleInitialCount);
|
||||
}
|
||||
|
||||
static void WaitForInterrupt(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||
if (hasMwait && monitorAddr != nullptr) {
|
||||
Hal::IdleWait(monitorAddr);
|
||||
} else {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
// Timer IRQ handler: BSP handles timekeeping and the few timer-driven
|
||||
// fallbacks that are still required; APs only run scheduler accounting.
|
||||
static void TimerHandler(uint8_t) {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
uint32_t schedElapsedMs = (cpu->cpuIndex == 0) ? BSP_TICK_INTERVAL_MS : AP_TICK_INTERVAL_MS;
|
||||
|
||||
if (cpu->cpuIndex == 0) {
|
||||
// BSP: increment global tick count and poll devices
|
||||
g_tickCount.fetch_add(1, std::memory_order_relaxed);
|
||||
if (g_bspIdleOneShotArmed) {
|
||||
schedElapsedMs = g_bspIdleOneShotMs;
|
||||
g_bspIdleOneShotArmed = false;
|
||||
ProgramBspPeriodicTimer();
|
||||
}
|
||||
|
||||
g_tickCount.fetch_add(schedElapsedMs, std::memory_order_relaxed);
|
||||
|
||||
if (Drivers::Net::E1000E::RequiresPolling()) {
|
||||
Drivers::Net::E1000E::Poll();
|
||||
}
|
||||
if (Drivers::USB::Xhci::HasDeferredWork()) {
|
||||
Drivers::USB::Xhci::ProcessDeferredWork();
|
||||
}
|
||||
Drivers::USB::HidKeyboard::Tick();
|
||||
}
|
||||
|
||||
if (g_schedEnabled) {
|
||||
Sched::Tick();
|
||||
Sched::Tick(schedElapsedMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,16 +190,11 @@ namespace Timekeeping {
|
||||
Hal::RegisterIrqHandler(Hal::IRQ_TIMER, TimerHandler);
|
||||
|
||||
// Configure APIC timer: periodic mode, vector 32
|
||||
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||
ProgramBspPeriodicTimer();
|
||||
|
||||
// Set initial count for 1ms intervals (1000 Hz tick rate)
|
||||
uint32_t initialCount = g_ticksPerMs;
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, initialCount);
|
||||
|
||||
KernelLogStream(OK, "Timer") << "APIC timer started: " << base::dec << (uint64_t)TIMER_HZ
|
||||
<< " Hz periodic, initial count=" << (uint64_t)initialCount;
|
||||
KernelLogStream(OK, "Timer") << "APIC timer started: BSP " << base::dec
|
||||
<< (uint64_t)BSP_TIMER_HZ << " Hz periodic, initial count="
|
||||
<< (uint64_t)CountForIntervalMs(BSP_TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
void ApicTimerReinitialize() {
|
||||
@@ -150,10 +206,8 @@ namespace Timekeeping {
|
||||
// Reprogram the APIC timer registers (they were lost during S3).
|
||||
// The calibrated g_ticksPerMs value is still valid (it's in RAM).
|
||||
// The IRQ handler registration also survives (it's a function pointer array in RAM).
|
||||
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs);
|
||||
g_bspIdleOneShotArmed = false;
|
||||
ProgramBspPeriodicTimer();
|
||||
|
||||
KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume";
|
||||
}
|
||||
@@ -176,11 +230,67 @@ namespace Timekeeping {
|
||||
// identical. This avoids PIT contention during AP boot.
|
||||
if (g_ticksPerMs == 0) return;
|
||||
|
||||
// Configure periodic timer at 1000 Hz (same vector as BSP)
|
||||
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC;
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs);
|
||||
// Configure a coarser periodic timer on APs. The scheduler still gets
|
||||
// a 10 ms time slice, but idle APs stop taking 1000 timer interrupts/sec.
|
||||
ProgramTimer(true, AP_TICK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||
auto* cpu = Smp::GetCurrentCpuData();
|
||||
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
|
||||
WaitForInterrupt(hasMwait, monitorAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
Sched::RunBspMaintenance();
|
||||
|
||||
if (Sched::HasReadyProcesses()) {
|
||||
Sched::Schedule();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t waitMs = BSP_TICK_INTERVAL_MS;
|
||||
waitMs = BSP_IDLE_MAX_INTERVAL_MS;
|
||||
|
||||
uint64_t now = GetTicks();
|
||||
uint64_t nextDeadline = Sched::GetNextDeadlineTick();
|
||||
if (nextDeadline != 0) {
|
||||
if (nextDeadline <= now) {
|
||||
waitMs = BSP_TICK_INTERVAL_MS;
|
||||
} else {
|
||||
uint64_t untilDeadline = nextDeadline - now;
|
||||
if (untilDeadline < waitMs) {
|
||||
waitMs = (uint32_t)untilDeadline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (waitMs == 0) {
|
||||
waitMs = BSP_TICK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
asm volatile("cli" ::: "memory");
|
||||
ProgramBspIdleOneShotTimer(waitMs);
|
||||
asm volatile("sti" ::: "memory");
|
||||
|
||||
WaitForInterrupt(hasMwait, monitorAddr);
|
||||
|
||||
asm volatile("cli" ::: "memory");
|
||||
if (g_bspIdleOneShotArmed) {
|
||||
uint32_t currentCount = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_CURRENT);
|
||||
uint32_t elapsedTicks = (g_bspIdleInitialCount > currentCount)
|
||||
? (g_bspIdleInitialCount - currentCount)
|
||||
: 0;
|
||||
uint32_t elapsedMs = (g_ticksPerMs > 0) ? (elapsedTicks / g_ticksPerMs) : 0;
|
||||
if (elapsedMs > 0) {
|
||||
g_tickCount.fetch_add(elapsedMs, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
g_bspIdleOneShotArmed = false;
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0);
|
||||
ProgramBspPeriodicTimer();
|
||||
}
|
||||
asm volatile("sti" ::: "memory");
|
||||
}
|
||||
|
||||
void Sleep(uint64_t ms) {
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace Timekeeping {
|
||||
// Enable scheduler tick (called after scheduler is initialized)
|
||||
void EnableSchedulerTick();
|
||||
|
||||
// Enter one idle wait cycle for the current CPU. The BSP uses a
|
||||
// one-shot LAPIC timer while idle; APs keep their existing simple wait.
|
||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr = nullptr);
|
||||
|
||||
// Busy-wait sleep for the given number of milliseconds
|
||||
void Sleep(uint64_t ms);
|
||||
};
|
||||
|
||||
@@ -267,6 +267,36 @@ static void text_fit(const char* src, char* out, int out_len, int max_w) {
|
||||
montauk::strncpy(out, ell, out_len - 1);
|
||||
}
|
||||
|
||||
static void format_resolved_transport_line(const IppUri& uri, char* out, int out_len) {
|
||||
if (!out || out_len <= 0) return;
|
||||
out[0] = '\0';
|
||||
|
||||
const char* transport = uri.use_tls ? "TLS" : "Plain TCP";
|
||||
uint32_t ip = 0;
|
||||
if (!parse_ipv4_literal(uri.host, &ip))
|
||||
ip = g_app.probe_caps.resolved_ip;
|
||||
|
||||
if (ip != 0) {
|
||||
char ip_text[20];
|
||||
format_ipv4(ip_text, sizeof(ip_text), ip);
|
||||
snprintf(out, out_len, "Resolved: %s Transport: %s", ip_text, transport);
|
||||
return;
|
||||
}
|
||||
|
||||
if (host_looks_like_mdns(uri.host)) {
|
||||
snprintf(out, out_len,
|
||||
"Resolved: unavailable (.local/mDNS not supported yet) Transport: %s",
|
||||
transport);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_app.probe_valid)
|
||||
snprintf(out, out_len, "Resolved: unavailable Transport: %s", transport);
|
||||
else
|
||||
snprintf(out, out_len, "Resolved: run Probe to resolve current printer Transport: %s",
|
||||
transport);
|
||||
}
|
||||
|
||||
static bool main_mouse_in_rect(const Rect& rect) {
|
||||
return rect.contains(g_app.mouse_x, g_app.mouse_y);
|
||||
}
|
||||
@@ -807,17 +837,7 @@ static void render_details(Canvas& c, const Layout& lo, const mtk::Theme& theme)
|
||||
c.text(x, y, line, dim);
|
||||
y += sfh + 4;
|
||||
|
||||
uint32_t ip = 0;
|
||||
if (resolve_host(normalized.host, &ip)) {
|
||||
char ip_text[20];
|
||||
format_ipv4(ip_text, sizeof(ip_text), ip);
|
||||
snprintf(line, sizeof(line), "Resolved: %s Transport: %s", ip_text,
|
||||
normalized.use_tls ? "TLS" : "Plain TCP");
|
||||
} else if (host_looks_like_mdns(normalized.host)) {
|
||||
safe_copy(line, sizeof(line), "Resolved: unavailable (.local/mDNS not supported yet)");
|
||||
} else {
|
||||
safe_copy(line, sizeof(line), "Resolved: unavailable");
|
||||
}
|
||||
format_resolved_transport_line(normalized, line, sizeof(line));
|
||||
text_fit(line, line, sizeof(line), w);
|
||||
c.text(x, y, line, dim);
|
||||
y += sfh + 4;
|
||||
@@ -943,8 +963,6 @@ static void render_details(Canvas& c, const Layout& lo, const mtk::Theme& theme)
|
||||
}
|
||||
|
||||
static void render() {
|
||||
refresh_state();
|
||||
|
||||
mtk::StandaloneHost host(&g_win);
|
||||
Canvas c = host.canvas();
|
||||
mtk::Theme theme = printers_theme();
|
||||
|
||||
Reference in New Issue
Block a user