feat: add threading to Scheduler, fix desktop background selection freeze issue

This commit is contained in:
2026-05-23 16:08:25 +02:00
parent a44a65d432
commit cd159235ca
15 changed files with 895 additions and 117 deletions
@@ -163,8 +163,16 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in
montauk::strcpy(fullpath, st->desktop->home_dir);
str_append(fullpath, "/", 256);
str_append(fullpath, st->wp_files.names[i], 256);
wallpaper_load(&s, fullpath,
st->desktop->screen_w, st->desktop->screen_h);
// Decode and scale off the UI thread so rapid cycling
// between high-res images stays responsive. The radio state
// and persisted path update immediately; the pixel buffer
// arrives asynchronously and is swapped in by
// wallpaper_poll() on the next frame.
s.bg_image = true;
s.bg_gradient = false;
montauk::strncpy(s.bg_image_path, fullpath, 127);
wallpaper_load_async(fullpath,
st->desktop->screen_w, st->desktop->screen_h);
desktop_mark_background_dirty(st->desktop);
settings_persist(st);
return true;
+6
View File
@@ -684,6 +684,12 @@ void gui::desktop_run(DesktopState* ds) {
uint64_t now = montauk::get_milliseconds();
sceneChanged |= desktop_refresh_panel_state(ds, now);
// Pick up any wallpaper buffer produced by a background loader.
if (wallpaper_poll(&ds->settings)) {
desktop_mark_background_dirty(ds);
sceneChanged = true;
}
uint64_t launcherBlinkToken = desktop_launcher_blink_token(ds, now);
if (launcherBlinkToken != lastLauncherBlinkToken) {
lastLauncherBlinkToken = launcherBlinkToken;
+138 -28
View File
@@ -9,6 +9,7 @@
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <montauk/thread.h>
#include <gui/gui.hpp>
#include <gui/desktop.hpp>
@@ -29,66 +30,52 @@ namespace gui {
// Wallpaper loading
// ============================================================================
// Load a JPEG file and scale it to cover the given screen dimensions.
// Stores the scaled ARGB pixel buffer in settings. Returns true on success.
inline bool wallpaper_load(DesktopSettings* s, const char* path,
int screen_w, int screen_h) {
// Free existing wallpaper
if (s->bg_wallpaper) {
montauk::mfree(s->bg_wallpaper);
s->bg_wallpaper = nullptr;
s->bg_wallpaper_w = 0;
s->bg_wallpaper_h = 0;
}
// Read file
// Decode `path` and scale it to cover (screen_w x screen_h). Returns a newly
// montauk::malloc'd ARGB buffer the caller owns, or nullptr on failure. This
// is the heavy lifting; it touches no shared state and is safe to run from a
// worker thread.
inline uint32_t* wallpaper_decode_scaled(const char* path,
int screen_w, int screen_h) {
int fd = montauk::open(path);
if (fd < 0) return false;
if (fd < 0) return nullptr;
uint64_t size = montauk::getsize(fd);
if (size == 0 || size > 16 * 1024 * 1024) {
montauk::close(fd);
return false;
return nullptr;
}
uint8_t* filedata = (uint8_t*)montauk::malloc(size);
if (!filedata) { montauk::close(fd); return false; }
if (!filedata) { montauk::close(fd); return nullptr; }
int bytes_read = montauk::read(fd, filedata, 0, size);
montauk::close(fd);
if (bytes_read <= 0) { montauk::mfree(filedata); return false; }
if (bytes_read <= 0) { montauk::mfree(filedata); return nullptr; }
// Decode JPEG
int img_w, img_h, channels;
unsigned char* rgb = stbi_load_from_memory(filedata, bytes_read,
&img_w, &img_h, &channels, 3);
montauk::mfree(filedata);
if (!rgb) return false;
if (!rgb) return nullptr;
// Scale to cover screen (crop to fill, maintain aspect ratio)
int dst_w = screen_w;
int dst_h = screen_h;
uint32_t* scaled = (uint32_t*)montauk::malloc((uint64_t)dst_w * dst_h * 4);
if (!scaled) { stbi_image_free(rgb); return false; }
if (!scaled) { stbi_image_free(rgb); return nullptr; }
// Compute source crop region for "cover" scaling
int src_crop_w, src_crop_h, src_x0, src_y0;
if ((int64_t)img_w * dst_h > (int64_t)img_h * dst_w) {
// Image is wider — crop sides
src_crop_h = img_h;
src_crop_w = (int)((int64_t)img_h * dst_w / dst_h);
src_x0 = (img_w - src_crop_w) / 2;
src_y0 = 0;
} else {
// Image is taller — crop top/bottom
src_crop_w = img_w;
src_crop_h = (int)((int64_t)img_w * dst_h / dst_w);
src_x0 = 0;
src_y0 = (img_h - src_crop_h) / 2;
}
// Nearest-neighbor scale from cropped region to destination
for (int y = 0; y < dst_h; y++) {
int sy = src_y0 + (int)((int64_t)y * src_crop_h / dst_h);
if (sy < 0) sy = 0;
@@ -106,14 +93,137 @@ inline bool wallpaper_load(DesktopSettings* s, const char* path,
}
stbi_image_free(rgb);
return scaled;
}
// Synchronous loader retained for code paths that genuinely want to block
// (e.g. desktop boot, where there is no UI to keep responsive yet).
inline bool wallpaper_load(DesktopSettings* s, const char* path,
int screen_w, int screen_h) {
uint32_t* scaled = wallpaper_decode_scaled(path, screen_w, screen_h);
if (!scaled) return false;
if (s->bg_wallpaper) montauk::mfree(s->bg_wallpaper);
s->bg_wallpaper = scaled;
s->bg_wallpaper_w = dst_w;
s->bg_wallpaper_h = dst_h;
s->bg_wallpaper_w = screen_w;
s->bg_wallpaper_h = screen_h;
montauk::strncpy(s->bg_image_path, path, 127);
s->bg_image = true;
s->bg_gradient = false;
return true;
}
// ============================================================================
// Async wallpaper loader
//
// Decoding a screen-sized JPEG blocks the desktop event loop. The async
// loader spawns a worker thread per click; rapid cycling is tolerated via a
// generation counter -- only the highest-gen worker's output is published.
// The main thread calls wallpaper_poll() each frame to swap in any new
// buffer (it owns the actual handoff and the free of the previous one).
// ============================================================================
struct WallpaperPublishSlot {
montauk::Mutex lock;
uint64_t latest_gen; // bumped on every load request
uint64_t pending_gen; // gen of pending_pixels (0 = empty)
uint64_t applied_gen; // highest gen consumed by the main thread
uint32_t* pending_pixels; // worker-produced ARGB buffer awaiting handoff
int pending_w, pending_h;
char pending_path[128];
};
inline WallpaperPublishSlot& wallpaper_slot() {
static WallpaperPublishSlot s{};
return s;
}
struct WallpaperJob {
uint64_t gen;
int screen_w, screen_h;
char path[256];
};
inline int wallpaper_worker(void* raw) {
auto* job = (WallpaperJob*)raw;
uint32_t* scaled = wallpaper_decode_scaled(job->path, job->screen_w, job->screen_h);
auto& slot = wallpaper_slot();
slot.lock.lock();
bool publish = scaled != nullptr
&& job->gen > slot.applied_gen
&& job->gen > slot.pending_gen;
if (publish) {
if (slot.pending_pixels) {
// A stale (lower-gen) pending buffer was never consumed -- drop it.
montauk::mfree(slot.pending_pixels);
}
slot.pending_pixels = scaled;
slot.pending_w = job->screen_w;
slot.pending_h = job->screen_h;
montauk::strncpy(slot.pending_path, job->path, 127);
slot.pending_gen = job->gen;
}
slot.lock.unlock();
if (!publish && scaled) montauk::mfree(scaled);
montauk::mfree(job);
return 0;
}
// Kick off a background decode. Returns true if the worker thread was
// spawned; the actual wallpaper swap happens later when wallpaper_poll
// observes the published buffer.
inline bool wallpaper_load_async(const char* path,
int screen_w, int screen_h) {
auto* job = (WallpaperJob*)montauk::malloc(sizeof(WallpaperJob));
if (!job) return false;
montauk::strncpy(job->path, path, 255);
job->screen_w = screen_w;
job->screen_h = screen_h;
auto& slot = wallpaper_slot();
slot.lock.lock();
job->gen = ++slot.latest_gen;
slot.lock.unlock();
int tid = montauk::thread_spawn(&wallpaper_worker, job);
if (tid < 0) {
montauk::mfree(job);
return false;
}
return true;
}
// If a worker has produced a newer buffer than the main thread has applied,
// swap it into `s` and free the previous wallpaper. Returns true if a new
// wallpaper was installed (caller should mark the background dirty).
inline bool wallpaper_poll(DesktopSettings* s) {
auto& slot = wallpaper_slot();
slot.lock.lock();
if (slot.pending_gen == 0 || slot.pending_gen <= slot.applied_gen) {
slot.lock.unlock();
return false;
}
uint32_t* new_pixels = slot.pending_pixels;
int new_w = slot.pending_w;
int new_h = slot.pending_h;
char new_path[128];
montauk::strncpy(new_path, slot.pending_path, 127);
new_path[127] = '\0';
slot.applied_gen = slot.pending_gen;
slot.pending_pixels = nullptr;
slot.pending_gen = 0;
slot.lock.unlock();
uint32_t* old = s->bg_wallpaper;
s->bg_wallpaper = new_pixels;
s->bg_wallpaper_w = new_w;
s->bg_wallpaper_h = new_h;
montauk::strncpy(s->bg_image_path, new_path, 127);
s->bg_image = true;
s->bg_gradient = false;
if (old) montauk::mfree(old);
return true;
}