fix: fix login wallpaper startup delay
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 179
|
#define MONTAUK_BUILD_NUMBER 182
|
||||||
|
|||||||
+39
-1
@@ -45,7 +45,12 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t VmProtWrite = 2;
|
static constexpr uint64_t VmProtWrite = 2;
|
||||||
static constexpr uint64_t VmProtExec = 4;
|
static constexpr uint64_t VmProtExec = 4;
|
||||||
|
|
||||||
inline uint64_t Sys_MapAnonymous(uint64_t size, uint64_t prot) {
|
// Sys_MapAnonymous flags. Populate commits the whole range at mapping
|
||||||
|
// time; without it every page is materialized on first touch.
|
||||||
|
static constexpr uint64_t VmFlagPopulate = 1;
|
||||||
|
|
||||||
|
inline uint64_t Sys_MapAnonymous(uint64_t size, uint64_t prot,
|
||||||
|
uint64_t flags = 0) {
|
||||||
auto* proc = Sched::GetCurrentProcessPtr();
|
auto* proc = Sched::GetCurrentProcessPtr();
|
||||||
if (proc == nullptr) return 0;
|
if (proc == nullptr) return 0;
|
||||||
int slot = GetCurrentSlot();
|
int slot = GetCurrentSlot();
|
||||||
@@ -83,6 +88,33 @@ namespace montauk::abi {
|
|||||||
g_heapAllocs[slot] = new HeapAlloc { userVa, numPages, prot, allocationId,
|
g_heapAllocs[slot] = new HeapAlloc { userVa, numPages, prot, allocationId,
|
||||||
g_heapAllocs[slot] };
|
g_heapAllocs[slot] };
|
||||||
|
|
||||||
|
// Populate is best effort: commit as much of the range as the frame
|
||||||
|
// allocator will give up front, and leave the remainder to the fault
|
||||||
|
// path. A caller that is about to touch every page (a decode buffer,
|
||||||
|
// a heap slab) then pays one loop instead of one trap, one mutex
|
||||||
|
// acquire and one VMA walk per 4 KiB.
|
||||||
|
if ((flags & VmFlagPopulate) != 0) {
|
||||||
|
bool writable = (prot & VmProtWrite) != 0;
|
||||||
|
bool executable = (prot & VmProtExec) != 0;
|
||||||
|
// Bounded so one syscall cannot pin an unbounded amount of memory
|
||||||
|
// with the slot's heap lock held. Anything past the cap faults in.
|
||||||
|
static constexpr uint64_t MaxPopulatePages = 64 * 1024 * 1024 / 0x1000;
|
||||||
|
uint64_t populate = numPages < MaxPopulatePages ? numPages
|
||||||
|
: MaxPopulatePages;
|
||||||
|
for (uint64_t i = 0; i < populate; i++) {
|
||||||
|
uint64_t pageVa = userVa + i * 0x1000ULL;
|
||||||
|
void* page = Memory::g_pfa->AllocateZeroed();
|
||||||
|
if (page == nullptr) break;
|
||||||
|
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||||
|
if (!Memory::VMM::Paging::MapUserInPermissions(
|
||||||
|
proc->pml4Phys, phys, pageVa, writable, executable)) {
|
||||||
|
Memory::g_pfa->Free(page);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Sched::g_allocatedPages[slot]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g_heapLocks[slot].Release();
|
g_heapLocks[slot].Release();
|
||||||
return userVa;
|
return userVa;
|
||||||
}
|
}
|
||||||
@@ -91,6 +123,12 @@ namespace montauk::abi {
|
|||||||
return Sys_MapAnonymous(size, VmProtRead | VmProtWrite);
|
return Sys_MapAnonymous(size, VmProtRead | VmProtWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As Sys_Alloc, but commits the pages immediately instead of faulting them
|
||||||
|
// in one at a time.
|
||||||
|
inline uint64_t Sys_AllocEager(uint64_t size) {
|
||||||
|
return Sys_MapAnonymous(size, VmProtRead | VmProtWrite, VmFlagPopulate);
|
||||||
|
}
|
||||||
|
|
||||||
// Reset heap allocation tracking for a process slot.
|
// Reset heap allocation tracking for a process slot.
|
||||||
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
|
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
|
||||||
inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) {
|
inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) {
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ namespace montauk::abi {
|
|||||||
(int)frame->arg4);
|
(int)frame->arg4);
|
||||||
case SYS_ALLOC:
|
case SYS_ALLOC:
|
||||||
return (int64_t)Sys_Alloc(frame->arg1);
|
return (int64_t)Sys_Alloc(frame->arg1);
|
||||||
|
case SYS_ALLOC_EAGER:
|
||||||
|
return (int64_t)Sys_AllocEager(frame->arg1);
|
||||||
case SYS_FREE:
|
case SYS_FREE:
|
||||||
Sys_Free(frame->arg1);
|
Sys_Free(frame->arg1);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -338,6 +338,11 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
|
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
|
||||||
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
|
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
|
||||||
|
|
||||||
|
/* Heap.hpp -- as SYS_ALLOC, but commits every page up front instead of
|
||||||
|
faulting them in one at a time. For buffers the caller is about to
|
||||||
|
touch in full (image decode, heap slabs). */
|
||||||
|
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
|
||||||
|
|
||||||
/* Kernel-owned process capabilities. User identities may namespace
|
/* Kernel-owned process capabilities. User identities may namespace
|
||||||
per-user resources, but never participate in authorization decisions. */
|
per-user resources, but never participate in authorization decisions. */
|
||||||
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ namespace Fs {
|
|||||||
{"/config/init.toml", false, montauk::abi::CAP_USER_ADMIN},
|
{"/config/init.toml", false, montauk::abi::CAP_USER_ADMIN},
|
||||||
{"/config/ssh.toml", false, montauk::abi::CAP_USER_ADMIN},
|
{"/config/ssh.toml", false, montauk::abi::CAP_USER_ADMIN},
|
||||||
{"/config/capabilities.toml",false, montauk::abi::CAP_USER_ADMIN},
|
{"/config/capabilities.toml",false, montauk::abi::CAP_USER_ADMIN},
|
||||||
|
// Pre-scaled wallpaper the login screen blits before it has decoded
|
||||||
|
// anything. It is drawn on a screen that is about to take a password,
|
||||||
|
// so it must not be plantable by an unprivileged process.
|
||||||
|
{"/config/wallpaper.cache", false, montauk::abi::CAP_USER_ADMIN},
|
||||||
// Read by the Bluetooth driver at controller bring-up.
|
// Read by the Bluetooth driver at controller bring-up.
|
||||||
{"/config/bluetooth.toml", false, montauk::abi::CAP_DEVICE_ADMIN},
|
{"/config/bluetooth.toml", false, montauk::abi::CAP_DEVICE_ADMIN},
|
||||||
// Program images. Capability grants are keyed on binary path, so a
|
// Program images. Capability grants are keyed on binary path, so a
|
||||||
|
|||||||
@@ -355,10 +355,18 @@ namespace Fs::Ramdisk {
|
|||||||
uint64_t newCap = entry.size;
|
uint64_t newCap = entry.size;
|
||||||
if (endOffset > newCap) newCap = endOffset;
|
if (endOffset > newCap) newCap = endOffset;
|
||||||
if (newCap < 256) newCap = 256;
|
if (newCap < 256) newCap = 256;
|
||||||
// Round up to next power of 2 for growth
|
// Small files round to the next power of 2, so an appender grows
|
||||||
|
// in a few steps. Large ones round to a page instead: the kernel
|
||||||
|
// heap grows in physically contiguous runs, and doubling an 8 MiB
|
||||||
|
// write into a 16 MiB block asks the frame allocator for twice the
|
||||||
|
// contiguous span the file actually needs.
|
||||||
|
if (newCap < 64 * 1024) {
|
||||||
uint64_t rounded = 256;
|
uint64_t rounded = 256;
|
||||||
while (rounded < newCap) rounded *= 2;
|
while (rounded < newCap) rounded *= 2;
|
||||||
newCap = rounded;
|
newCap = rounded;
|
||||||
|
} else {
|
||||||
|
newCap = (newCap + 0xFFFULL) & ~0xFFFULL;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
||||||
if (newBuf == nullptr) return -1;
|
if (newBuf == nullptr) return -1;
|
||||||
@@ -374,8 +382,14 @@ namespace Fs::Ramdisk {
|
|||||||
|
|
||||||
// Grow buffer if needed
|
// Grow buffer if needed
|
||||||
if (endOffset > entry.capacity) {
|
if (endOffset > entry.capacity) {
|
||||||
uint64_t newCap = entry.capacity;
|
// Double while small, then grow in fixed 1 MiB steps. Doubling all
|
||||||
while (newCap < endOffset) newCap *= 2;
|
// the way keeps growth amortized but overshoots badly on multi-MiB
|
||||||
|
// files, and every byte of overshoot is a physically contiguous
|
||||||
|
// kernel-heap run this file holds for the rest of the boot.
|
||||||
|
static constexpr uint64_t MaxGrowStep = 1024 * 1024;
|
||||||
|
uint64_t newCap = entry.capacity < 256 ? 256 : entry.capacity;
|
||||||
|
while (newCap < endOffset)
|
||||||
|
newCap += (newCap < MaxGrowStep) ? newCap : MaxGrowStep;
|
||||||
|
|
||||||
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
uint8_t* newBuf = (uint8_t*)Memory::g_heap->Request(newCap);
|
||||||
if (newBuf == nullptr) return -1;
|
if (newBuf == nullptr) return -1;
|
||||||
|
|||||||
@@ -252,6 +252,9 @@ namespace montauk::abi {
|
|||||||
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal
|
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal
|
||||||
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
|
static constexpr uint64_t SYS_SPAWN_CAPS = 185;
|
||||||
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
|
static constexpr uint64_t SYS_SPAWN_REDIR_CAPS = 186;
|
||||||
|
// As SYS_ALLOC, but commits every page up front instead of faulting them
|
||||||
|
// in one at a time. For buffers the caller is about to touch in full.
|
||||||
|
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
|
||||||
|
|
||||||
/* Kernel-owned process capabilities. User identities may namespace
|
/* Kernel-owned process capabilities. User identities may namespace
|
||||||
per-user resources, but never participate in authorization decisions. */
|
per-user resources, but never participate in authorization decisions. */
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ extern "C" {
|
|||||||
#define MTK_SYS_USB_BULK_IN_READ 184
|
#define MTK_SYS_USB_BULK_IN_READ 184
|
||||||
#define MTK_SYS_SPAWN_CAPS 185
|
#define MTK_SYS_SPAWN_CAPS 185
|
||||||
#define MTK_SYS_SPAWN_REDIR_CAPS 186
|
#define MTK_SYS_SPAWN_REDIR_CAPS 186
|
||||||
|
#define MTK_SYS_ALLOC_EAGER 187
|
||||||
/* @SYSCALLS-END */
|
/* @SYSCALLS-END */
|
||||||
|
|
||||||
#define MTK_SOCK_TCP 1
|
#define MTK_SOCK_TCP 1
|
||||||
|
|||||||
@@ -191,6 +191,10 @@ namespace montauk {
|
|||||||
|
|
||||||
// Memory
|
// Memory
|
||||||
inline void* alloc(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC, size); }
|
inline void* alloc(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC, size); }
|
||||||
|
// As alloc(), but the kernel commits the whole range immediately. Use it
|
||||||
|
// for a buffer that is about to be written end to end: the lazy path costs
|
||||||
|
// one page fault, one mutex acquire and one VMA walk per 4 KiB.
|
||||||
|
inline void* alloc_eager(uint64_t size) { return (void*)syscall1(montauk::abi::SYS_ALLOC_EAGER, size); }
|
||||||
inline void free(void* ptr) { syscall1(montauk::abi::SYS_FREE, (uint64_t)ptr); }
|
inline void free(void* ptr) { syscall1(montauk::abi::SYS_FREE, (uint64_t)ptr); }
|
||||||
|
|
||||||
// Timekeeping
|
// Timekeeping
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ static inline long _mtk_syscall4(long nr, long a1, long a2, long a3, long a4) {
|
|||||||
#define SYS_CLOSE 9
|
#define SYS_CLOSE 9
|
||||||
#define SYS_READDIR 10
|
#define SYS_READDIR 10
|
||||||
#define SYS_ALLOC 11
|
#define SYS_ALLOC 11
|
||||||
|
#define SYS_ALLOC_EAGER 187
|
||||||
#define SYS_FREE 12
|
#define SYS_FREE 12
|
||||||
#define SYS_GETMILLISECONDS 14
|
#define SYS_GETMILLISECONDS 14
|
||||||
#define SYS_GETCHAR 18
|
#define SYS_GETCHAR 18
|
||||||
@@ -679,6 +680,14 @@ int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; }
|
|||||||
#define HEAP_ALIGN 16ULL
|
#define HEAP_ALIGN 16ULL
|
||||||
#define DIRECT_THRESHOLD (256ULL * 1024ULL)
|
#define DIRECT_THRESHOLD (256ULL * 1024ULL)
|
||||||
|
|
||||||
|
/* A direct mapping is one object the caller sized itself, so it is nearly
|
||||||
|
always written end to end (an image buffer, a loaded file). Committing it
|
||||||
|
up front replaces one page fault, one kernel mutex acquire and one VMA walk
|
||||||
|
per 4 KiB with a single loop -- thousands of traps for a decoded image.
|
||||||
|
Beyond the cap, stay lazy: a very large mapping is more likely to be a
|
||||||
|
sparsely touched reservation, and eager commit would pin the lot. */
|
||||||
|
#define EAGER_DIRECT_LIMIT (32ULL * 1024ULL * 1024ULL)
|
||||||
|
|
||||||
struct HeapHeader {
|
struct HeapHeader {
|
||||||
uint64_t magic;
|
uint64_t magic;
|
||||||
uint64_t requested_size;
|
uint64_t requested_size;
|
||||||
@@ -855,8 +864,10 @@ static void *heap_malloc_locked(size_t size) {
|
|||||||
if (needed >= DIRECT_THRESHOLD) {
|
if (needed >= DIRECT_THRESHOLD) {
|
||||||
if (needed > UINT64_MAX - 0xFFFULL) return NULL;
|
if (needed > UINT64_MAX - 0xFFFULL) return NULL;
|
||||||
uint64_t mapping_size = (needed + 0xFFFULL) & ~0xFFFULL;
|
uint64_t mapping_size = (needed + 0xFFFULL) & ~0xFFFULL;
|
||||||
|
long alloc_nr = (mapping_size <= EAGER_DIRECT_LIMIT)
|
||||||
|
? SYS_ALLOC_EAGER : SYS_ALLOC;
|
||||||
struct HeapHeader *hdr = (struct HeapHeader *)
|
struct HeapHeader *hdr = (struct HeapHeader *)
|
||||||
_mtk_syscall1(SYS_ALLOC, (long)mapping_size);
|
_mtk_syscall1(alloc_nr, (long)mapping_size);
|
||||||
if (hdr == NULL) return NULL;
|
if (hdr == NULL) return NULL;
|
||||||
hdr->magic = DIRECT_MAGIC;
|
hdr->magic = DIRECT_MAGIC;
|
||||||
hdr->requested_size = size;
|
hdr->requested_size = size;
|
||||||
|
|||||||
@@ -11,25 +11,144 @@ namespace {
|
|||||||
// Shipped fallback shown when no wallpaper is configured (see NOTICES.txt).
|
// Shipped fallback shown when no wallpaper is configured (see NOTICES.txt).
|
||||||
constexpr const char* kDefaultWallpaperPath = "0:/os/wallpapers/default.jpg";
|
constexpr const char* kDefaultWallpaperPath = "0:/os/wallpapers/default.jpg";
|
||||||
|
|
||||||
|
// Screen-sized, already tinted pixels, so a re-login skips the JPEG decode and
|
||||||
|
// the rescale entirely. Kernel-protected (CAP_USER_ADMIN) because it is drawn
|
||||||
|
// on a screen that is about to take a password.
|
||||||
|
constexpr const char* kCachePath = "0:/config/wallpaper.cache";
|
||||||
|
|
||||||
|
constexpr uint32_t kCacheMagic = 0x4350574DU; // "MWPC"
|
||||||
|
constexpr uint32_t kCacheVersion = 1;
|
||||||
|
|
||||||
|
// The cache lives on the ramdisk, which is kernel heap: past this size it
|
||||||
|
// costs more memory for the rest of the boot than the decode it saves.
|
||||||
|
constexpr uint64_t kMaxCacheBytes = 32ULL * 1024 * 1024;
|
||||||
|
|
||||||
constexpr uint32_t kLoginOverlayAlpha = 0x38;
|
constexpr uint32_t kLoginOverlayAlpha = 0x38;
|
||||||
constexpr uint32_t kLoginOverlayInvAlpha = 255 - kLoginOverlayAlpha;
|
constexpr uint32_t kLoginOverlayInvAlpha = 255 - kLoginOverlayAlpha;
|
||||||
|
|
||||||
|
// Fixed-size record. The pixels follow it in the same allocation, so the size
|
||||||
|
// must stay a multiple of 16 to keep them aligned.
|
||||||
|
struct CacheHeader {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t version;
|
||||||
|
int32_t width; // screen the pixels were scaled for
|
||||||
|
int32_t height;
|
||||||
|
uint64_t sourceSize; // source image, as it was when baked
|
||||||
|
int64_t sourceMtime;
|
||||||
|
uint32_t overlayAlpha; // tint baked into the pixels
|
||||||
|
uint32_t reserved;
|
||||||
|
char sourcePath[256];
|
||||||
|
uint64_t padding;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(CacheHeader) % 16 == 0,
|
||||||
|
"cache header must stay 16-byte aligned");
|
||||||
|
|
||||||
static uint8_t dim_component(uint8_t value) {
|
static uint8_t dim_component(uint8_t value) {
|
||||||
uint32_t scaled = kLoginOverlayInvAlpha * value;
|
uint32_t scaled = kLoginOverlayInvAlpha * value;
|
||||||
return (uint8_t)((scaled + 1 + (scaled >> 8)) >> 8);
|
return (uint8_t)((scaled + 1 + (scaled >> 8)) >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
// Pick the configured wallpaper, falling back to the shipped one, and return
|
||||||
|
// the path that actually opens along with its stat record.
|
||||||
bool load_login_wallpaper(LoginState* ls) {
|
bool resolve_source(char* outPath, int cap, montauk::abi::FileStat& outStat) {
|
||||||
auto doc = montauk::config::load("desktop");
|
auto doc = montauk::config::load("desktop");
|
||||||
char wp[256];
|
char wp[256];
|
||||||
montauk::strncpy(wp, doc.get_string("wallpaper.path", ""), sizeof(wp));
|
montauk::strncpy(wp, doc.get_string("wallpaper.path", ""), sizeof(wp));
|
||||||
doc.destroy();
|
doc.destroy();
|
||||||
|
|
||||||
int fd = -1;
|
const char* candidates[2] = { wp, kDefaultWallpaperPath };
|
||||||
if (wp[0] != '\0') fd = montauk::open(wp);
|
for (int i = 0; i < 2; i++) {
|
||||||
if (fd < 0) fd = montauk::open(kDefaultWallpaperPath);
|
if (candidates[i][0] == '\0') continue;
|
||||||
|
int fd = montauk::open(candidates[i]);
|
||||||
|
if (fd < 0) continue;
|
||||||
|
montauk::close(fd);
|
||||||
|
montauk::strncpy(outPath, candidates[i], cap);
|
||||||
|
montauk::memset(&outStat, 0, sizeof(outStat));
|
||||||
|
montauk::stat(outPath, &outStat); // best effort: 0/0 still validates
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate one block holding the cache header followed by the screen-sized
|
||||||
|
// pixel buffer, so saving the cache is a single write with no extra copy.
|
||||||
|
uint8_t* allocate_blob(int w, int h) {
|
||||||
|
uint64_t bytes = sizeof(CacheHeader) + (uint64_t)w * h * 4;
|
||||||
|
return (uint8_t*)montauk::malloc(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool header_matches(const CacheHeader& h, const LoginState* ls,
|
||||||
|
const char* srcPath, const montauk::abi::FileStat& st) {
|
||||||
|
return h.magic == kCacheMagic
|
||||||
|
&& h.version == kCacheVersion
|
||||||
|
&& h.width == ls->screen_w
|
||||||
|
&& h.height == ls->screen_h
|
||||||
|
&& h.sourceSize == st.size
|
||||||
|
&& h.sourceMtime == st.mtime
|
||||||
|
&& h.overlayAlpha == kLoginOverlayAlpha
|
||||||
|
&& montauk::streq(h.sourcePath, srcPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_from_cache(LoginState* ls, const char* srcPath,
|
||||||
|
const montauk::abi::FileStat& st, uint8_t*& blob) {
|
||||||
|
int fd = montauk::open(kCachePath);
|
||||||
|
if (fd < 0) return false;
|
||||||
|
|
||||||
|
uint64_t pixelBytes = (uint64_t)ls->screen_w * ls->screen_h * 4;
|
||||||
|
bool ok = montauk::getsize(fd) == sizeof(CacheHeader) + pixelBytes;
|
||||||
|
|
||||||
|
CacheHeader header;
|
||||||
|
if (ok) ok = montauk::read(fd, (uint8_t*)&header, 0, sizeof(header))
|
||||||
|
== (int)sizeof(header);
|
||||||
|
if (ok) ok = header_matches(header, ls, srcPath, st);
|
||||||
|
if (!ok) {
|
||||||
|
montauk::close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blob == nullptr) blob = allocate_blob(ls->screen_w, ls->screen_h);
|
||||||
|
if (blob == nullptr) {
|
||||||
|
montauk::close(fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in one call: the buffer is already committed, so this is a single
|
||||||
|
// kernel-side memcpy out of the ramdisk.
|
||||||
|
int got = montauk::read(fd, blob + sizeof(CacheHeader), sizeof(CacheHeader),
|
||||||
|
pixelBytes);
|
||||||
|
montauk::close(fd);
|
||||||
|
return got == (int)pixelBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_to_cache(const LoginState* ls, const char* srcPath,
|
||||||
|
const montauk::abi::FileStat& st, uint8_t* blob) {
|
||||||
|
uint64_t pixelBytes = (uint64_t)ls->screen_w * ls->screen_h * 4;
|
||||||
|
uint64_t total = sizeof(CacheHeader) + pixelBytes;
|
||||||
|
if (total > kMaxCacheBytes) return;
|
||||||
|
|
||||||
|
CacheHeader* header = (CacheHeader*)blob;
|
||||||
|
montauk::memset(header, 0, sizeof(*header));
|
||||||
|
header->magic = kCacheMagic;
|
||||||
|
header->version = kCacheVersion;
|
||||||
|
header->width = ls->screen_w;
|
||||||
|
header->height = ls->screen_h;
|
||||||
|
header->sourceSize = st.size;
|
||||||
|
header->sourceMtime = st.mtime;
|
||||||
|
header->overlayAlpha = kLoginOverlayAlpha;
|
||||||
|
montauk::strncpy(header->sourcePath, srcPath, sizeof(header->sourcePath));
|
||||||
|
|
||||||
|
int fd = montauk::fcreate(kCachePath);
|
||||||
|
if (fd < 0) return; // read-only volume or no authority: not fatal
|
||||||
|
// One write, so the file is never briefly visible half-written and the
|
||||||
|
// ramdisk sizes its backing buffer once.
|
||||||
|
montauk::fwrite(fd, blob, 0, total);
|
||||||
|
montauk::close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the source image and scale it to the screen, writing tinted pixels
|
||||||
|
// into the blob's pixel area.
|
||||||
|
bool decode_and_scale(LoginState* ls, const char* srcPath, uint8_t*& blob) {
|
||||||
|
int fd = montauk::open(srcPath);
|
||||||
if (fd < 0) return false;
|
if (fd < 0) return false;
|
||||||
|
|
||||||
uint64_t size = montauk::getsize(fd);
|
uint64_t size = montauk::getsize(fd);
|
||||||
@@ -59,11 +178,12 @@ bool load_login_wallpaper(LoginState* ls) {
|
|||||||
|
|
||||||
int dst_w = ls->screen_w;
|
int dst_w = ls->screen_w;
|
||||||
int dst_h = ls->screen_h;
|
int dst_h = ls->screen_h;
|
||||||
uint32_t* scaled = (uint32_t*)montauk::malloc((uint64_t)dst_w * dst_h * 4);
|
if (blob == nullptr) blob = allocate_blob(dst_w, dst_h);
|
||||||
if (!scaled) {
|
if (!blob) {
|
||||||
stbi_image_free(rgb);
|
stbi_image_free(rgb);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
uint32_t* scaled = (uint32_t*)(blob + sizeof(CacheHeader));
|
||||||
|
|
||||||
int src_crop_w, src_crop_h, src_x0, src_y0;
|
int src_crop_w, src_crop_h, src_x0, src_y0;
|
||||||
if ((int64_t)img_w * dst_h > (int64_t)img_h * dst_w) {
|
if ((int64_t)img_w * dst_h > (int64_t)img_h * dst_w) {
|
||||||
@@ -78,29 +198,59 @@ bool load_login_wallpaper(LoginState* ls) {
|
|||||||
src_y0 = (img_h - src_crop_h) / 2;
|
src_y0 = (img_h - src_crop_h) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < dst_h; y++) {
|
// Source column per destination column, computed once. Inline, the same
|
||||||
int sy = src_y0 + (int)((int64_t)y * src_crop_h / dst_h);
|
// expression costs one 64-bit divide per pixel -- millions of them, and
|
||||||
if (sy < 0) sy = 0;
|
// idiv neither pipelines nor vectorizes.
|
||||||
if (sy >= img_h) sy = img_h - 1;
|
int* col = (int*)montauk::malloc((uint64_t)dst_w * sizeof(int));
|
||||||
|
if (!col) {
|
||||||
|
stbi_image_free(rgb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (int x = 0; x < dst_w; x++) {
|
for (int x = 0; x < dst_w; x++) {
|
||||||
int sx = src_x0 + (int)((int64_t)x * src_crop_w / dst_w);
|
int sx = src_x0 + (int)((int64_t)x * src_crop_w / dst_w);
|
||||||
if (sx < 0) sx = 0;
|
if (sx < 0) sx = 0;
|
||||||
if (sx >= img_w) sx = img_w - 1;
|
if (sx >= img_w) sx = img_w - 1;
|
||||||
int si = (sy * img_w + sx) * 3;
|
col[x] = sx * 3;
|
||||||
uint8_t r = dim_component(rgb[si]);
|
}
|
||||||
uint8_t g = dim_component(rgb[si + 1]);
|
|
||||||
uint8_t b = dim_component(rgb[si + 2]);
|
for (int y = 0; y < dst_h; y++) {
|
||||||
scaled[y * dst_w + x] = 0xFF000000u
|
int sy = src_y0 + (int)((int64_t)y * src_crop_h / dst_h);
|
||||||
| ((uint32_t)r << 16)
|
if (sy < 0) sy = 0;
|
||||||
| ((uint32_t)g << 8)
|
if (sy >= img_h) sy = img_h - 1;
|
||||||
| (uint32_t)b;
|
const unsigned char* row = rgb + (int64_t)sy * img_w * 3;
|
||||||
|
uint32_t* dst = scaled + (int64_t)y * dst_w;
|
||||||
|
for (int x = 0; x < dst_w; x++) {
|
||||||
|
const unsigned char* src = row + col[x];
|
||||||
|
dst[x] = 0xFF000000u
|
||||||
|
| ((uint32_t)dim_component(src[0]) << 16)
|
||||||
|
| ((uint32_t)dim_component(src[1]) << 8)
|
||||||
|
| (uint32_t)dim_component(src[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
montauk::mfree(col);
|
||||||
stbi_image_free(rgb);
|
stbi_image_free(rgb);
|
||||||
ls->bg_wallpaper = scaled;
|
return true;
|
||||||
ls->bg_wallpaper_w = dst_w;
|
}
|
||||||
ls->bg_wallpaper_h = dst_h;
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool load_login_wallpaper(LoginState* ls) {
|
||||||
|
char srcPath[256];
|
||||||
|
montauk::abi::FileStat st;
|
||||||
|
if (!resolve_source(srcPath, sizeof(srcPath), st)) return false;
|
||||||
|
|
||||||
|
uint8_t* blob = nullptr;
|
||||||
|
bool cached = load_from_cache(ls, srcPath, st, blob);
|
||||||
|
if (!cached && !decode_and_scale(ls, srcPath, blob)) {
|
||||||
|
if (blob) montauk::mfree(blob);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!cached) save_to_cache(ls, srcPath, st, blob);
|
||||||
|
|
||||||
|
ls->bg_wallpaper = (uint32_t*)(blob + sizeof(CacheHeader));
|
||||||
|
ls->bg_wallpaper_w = ls->screen_w;
|
||||||
|
ls->bg_wallpaper_h = ls->screen_h;
|
||||||
ls->has_wallpaper = true;
|
ls->has_wallpaper = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
gui::fonts::init();
|
gui::fonts::init();
|
||||||
montauk::set_mouse_bounds(ls->screen_w - 1, ls->screen_h - 1);
|
montauk::set_mouse_bounds(ls->screen_w - 1, ls->screen_h - 1);
|
||||||
load_login_wallpaper(ls);
|
|
||||||
|
|
||||||
// MTK theme (picks up the system accent). The compose buffer is only
|
// MTK theme (picks up the system accent). The compose buffer is only
|
||||||
// needed when the framebuffer pitch is not tightly packed; otherwise the
|
// needed when the framebuffer pitch is not tightly packed; otherwise the
|
||||||
@@ -83,6 +82,14 @@ extern "C" void _start() {
|
|||||||
maybe_run_setup_session();
|
maybe_run_setup_session();
|
||||||
initialize_login_mode(ls);
|
initialize_login_mode(ls);
|
||||||
|
|
||||||
|
// Put the login card on screen before touching the wallpaper. Decoding a
|
||||||
|
// multi-megapixel JPEG takes long enough to read as a hang if nothing has
|
||||||
|
// been painted yet; drawn first, it lands as a background appearing behind
|
||||||
|
// a screen the user can already type into. The loop below redraws with the
|
||||||
|
// wallpaper because first_frame is still set.
|
||||||
|
draw_login_screen(ls);
|
||||||
|
load_login_wallpaper(ls);
|
||||||
|
|
||||||
bool first_frame = true;
|
bool first_frame = true;
|
||||||
uint64_t input_serial = montauk::input_wait(0, 0);
|
uint64_t input_serial = montauk::input_wait(0, 0);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|||||||
Reference in New Issue
Block a user