feat: add screenshot app
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# Makefile for screenshot tool on MontaukOS
|
||||
# Copyright (c) 2026 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
else
|
||||
CXX := g++
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
PROJECT_ROOT := $(shell cd ../../.. && pwd)
|
||||
PROGRAMS := $(PROJECT_ROOT)/programs
|
||||
JPEGWRITE_LIB := $(PROGRAMS)/lib/libjpegwrite
|
||||
LIBC_LIB := $(PROGRAMS)/lib/libc
|
||||
PROG_INC := $(PROGRAMS)/include
|
||||
LINK_LD := $(PROGRAMS)/link.ld
|
||||
BINDIR := $(PROGRAMS)/bin
|
||||
OBJDIR := obj
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem $(PROJECT_ROOT)/kernel/freestnd-c-hdrs/x86_64/include \
|
||||
-isystem $(PROJECT_ROOT)/kernel/freestnd-cxx-hdrs/x86_64/include
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- Libraries ----
|
||||
|
||||
LIBS := $(JPEGWRITE_LIB)/libjpegwrite.a $(LIBC_LIB)/liblibc.a
|
||||
|
||||
# ---- Source files ----
|
||||
|
||||
SRCS := main.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/apps/screenshot/screenshot.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/apps/screenshot
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Screenshot Tool
|
||||
* Captures the screen framebuffer and saves as JPEG to the user's Pictures directory.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/config.h>
|
||||
|
||||
extern "C" {
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define STBI_WRITE_NO_STDIO
|
||||
#include <gui/stb_image_write.h>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// JPEG write callback - accumulates encoded bytes into a resizable buffer
|
||||
// ============================================================================
|
||||
|
||||
struct JpegBuffer {
|
||||
uint8_t* data;
|
||||
uint64_t size;
|
||||
uint64_t capacity;
|
||||
};
|
||||
|
||||
static void jpeg_write_callback(void* context, void* data, int size) {
|
||||
JpegBuffer* buf = (JpegBuffer*)context;
|
||||
if (size <= 0) return;
|
||||
|
||||
uint64_t needed = buf->size + (uint64_t)size;
|
||||
if (needed > buf->capacity) {
|
||||
uint64_t new_cap = buf->capacity * 2;
|
||||
if (new_cap < needed) new_cap = needed;
|
||||
uint8_t* new_data = (uint8_t*)montauk::realloc(buf->data, new_cap);
|
||||
if (!new_data) return;
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_cap;
|
||||
}
|
||||
montauk::memcpy(buf->data + buf->size, data, size);
|
||||
buf->size += (uint64_t)size;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Entry point
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void _start() {
|
||||
// Get framebuffer info before doing anything else
|
||||
Montauk::FbInfo fb;
|
||||
montauk::fb_info(&fb);
|
||||
|
||||
int width = (int)fb.width;
|
||||
int height = (int)fb.height;
|
||||
int pitch = (int)fb.pitch;
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
montauk::print("screenshot: no framebuffer available\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
// Map the hardware framebuffer (read-only access to screen pixels)
|
||||
uint32_t* hw_fb = (uint32_t*)montauk::fb_map();
|
||||
if (!hw_fb) {
|
||||
montauk::print("screenshot: failed to map framebuffer\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
// Copy framebuffer and convert from BGRA (0xAARRGGBB) to packed RGB
|
||||
uint64_t rgb_size = (uint64_t)width * height * 3;
|
||||
uint8_t* rgb = (uint8_t*)montauk::malloc(rgb_size);
|
||||
if (!rgb) {
|
||||
montauk::print("screenshot: out of memory\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
uint32_t* src_row = (uint32_t*)((uint8_t*)hw_fb + y * pitch);
|
||||
uint8_t* dst_row = rgb + y * width * 3;
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint32_t px = src_row[x];
|
||||
dst_row[x * 3 + 0] = (px >> 16) & 0xFF; // R
|
||||
dst_row[x * 3 + 1] = (px >> 8) & 0xFF; // G
|
||||
dst_row[x * 3 + 2] = px & 0xFF; // B
|
||||
}
|
||||
}
|
||||
|
||||
// Encode as JPEG (quality 100 - maximum quality)
|
||||
JpegBuffer jpeg;
|
||||
jpeg.data = (uint8_t*)montauk::malloc(256 * 1024);
|
||||
jpeg.size = 0;
|
||||
jpeg.capacity = 256 * 1024;
|
||||
|
||||
if (!jpeg.data) {
|
||||
montauk::mfree(rgb);
|
||||
montauk::print("screenshot: out of memory for JPEG buffer\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
int ok = stbi_write_jpg_to_func(jpeg_write_callback, &jpeg,
|
||||
width, height, 3, rgb, 100);
|
||||
montauk::mfree(rgb);
|
||||
|
||||
if (!ok || jpeg.size == 0) {
|
||||
montauk::mfree(jpeg.data);
|
||||
montauk::print("screenshot: JPEG encoding failed\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
// Build output path: 0:/users/<username>/Pictures/screenshot_YYYYMMDD_HHMMSS.jpg
|
||||
char username[32] = {};
|
||||
{
|
||||
auto doc = montauk::config::load("session");
|
||||
const char* name = doc.get_string("session.username", "");
|
||||
if (name[0] == '\0') {
|
||||
doc.destroy();
|
||||
montauk::mfree(jpeg.data);
|
||||
montauk::print("screenshot: no user session\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
montauk::strncpy(username, name, sizeof(username) - 1);
|
||||
doc.destroy();
|
||||
}
|
||||
|
||||
char home[128];
|
||||
snprintf(home, sizeof(home), "0:/users/%s", username);
|
||||
|
||||
// Ensure Pictures directory exists
|
||||
char pictures_dir[192];
|
||||
snprintf(pictures_dir, sizeof(pictures_dir), "%s/Pictures", home);
|
||||
montauk::fmkdir(pictures_dir);
|
||||
|
||||
// Build filename with timestamp
|
||||
Montauk::DateTime dt;
|
||||
montauk::gettime(&dt);
|
||||
|
||||
char filepath[256];
|
||||
snprintf(filepath, sizeof(filepath),
|
||||
"%s/screenshot_%04d%02d%02d_%02d%02d%02d.jpg",
|
||||
pictures_dir,
|
||||
(int)dt.Year, (int)dt.Month, (int)dt.Day,
|
||||
(int)dt.Hour, (int)dt.Minute, (int)dt.Second);
|
||||
|
||||
// Write JPEG file
|
||||
int fd = montauk::fcreate(filepath);
|
||||
if (fd < 0) {
|
||||
montauk::mfree(jpeg.data);
|
||||
montauk::print("screenshot: failed to create file\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
montauk::fwrite(fd, jpeg.data, 0, jpeg.size);
|
||||
montauk::close(fd);
|
||||
montauk::mfree(jpeg.data);
|
||||
|
||||
// Print confirmation
|
||||
char msg[320];
|
||||
snprintf(msg, sizeof(msg), "Screenshot saved: %s (%dx%d, %llu bytes)\n",
|
||||
filepath, width, height, (unsigned long long)jpeg.size);
|
||||
montauk::print(msg);
|
||||
|
||||
montauk::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Screenshot"
|
||||
binary = "screenshot.elf"
|
||||
icon = "screenshot.svg"
|
||||
|
||||
[menu]
|
||||
category = "System"
|
||||
visible = true
|
||||
Reference in New Issue
Block a user